
1. 使用基数树进行优化
在之前的实现中,PageCache 存在一个比较严重的性能瓶颈:查找页 ID 到 Span 的映射时需要加锁。由于 PageCache 中不断存在修改操作,如果在一个线程查询的过程中,另一个线程同时把这个 Span 拿走了,就会引发数据竞争问题。
更糟糕的是,这把锁直接锁住了整个 PageCache。没抢到锁的线程会阻塞等待,造成了严重的性能浪费。为了解决这个问题,我们引入了 Google 团队常用的数据结构:基数树(Radix Tree)。

感兴趣的可以阅读相关技术文章,例如 Linux Kernel 中的基数树实现。基数树的核心优势在于写之前会提前开好空间,写入过程中不会破坏原有结构。因为读写是分离的,线程 1 对一个位置读写时,线程 2 不可能对这个位置产生冲突。
TCMalloc 源码中有三个基数树的模板,适用于不同的场景。这里我们主要使用前两个模板。需要注意的是,该项目暂时只能在 32 位平台下使用基数树。
TCMalloc 基数树实现
#pragma once
#include "Common.h"
#include "ObjectPool.h"
// Single-level array
template <int BITS>
class TCMalloc_PageMap1 {
private:
static const int LENGTH = 1 << BITS;
void** array_;
public:
typedef uintptr_t Number;
explicit TCMalloc_PageMap1() {
size_t size = sizeof(void*) << BITS;
size_t alignSize = SizeClass::_RoundUp(size, << PAGE_SHIFT);
array_ = (**)(alignSize >> PAGE_SHIFT);
(array_, , (*) << BITS);
}
{
((k >> BITS) > ) { ; }
array_[k];
}
{
array_[k] = v;
}
};
< BITS>
{
:
PAGE_ID ROOT_BITS = ;
PAGE_ID ROOT_LENGTH = (PAGE_ID) << ROOT_BITS;
PAGE_ID LEAF_BITS = BITS - ROOT_BITS;
PAGE_ID LEAF_LENGTH = (PAGE_ID) << LEAF_BITS;
{
* values[LEAF_LENGTH];
};
Leaf* root_[ROOT_LENGTH];
* (*allocator_)();
:
Number;
{
(root_, , (root_));
();
}
{
Number i1 = k >> LEAF_BITS;
Number i2 = k & (LEAF_LENGTH - );
((k >> BITS) > || root_[i1] == ) { ; }
root_[i1]->values[i2];
}
{
Number i1 = k >> LEAF_BITS;
Number i2 = k & (LEAF_LENGTH - );
((k >> BITS) != || i1 >= ROOT_LENGTH) { ; }
(root_[i1] == ) {
(!(k, )) { ; }
}
(i2 >= LEAF_LENGTH) { ; }
root_[i1]->values[i2] = v;
}
{
(Number key = start; key <= start + n - ;) {
Number i1 = key >> LEAF_BITS;
(i1 >= ROOT_LENGTH) ;
(root_[i1] == ) {
ObjectPool<Leaf> leafPool;
Leaf* leaf = (Leaf*)leafPool.();
(leaf, , (*leaf));
root_[i1] = leaf;
}
key = ((key >> LEAF_BITS) + ) << LEAF_BITS;
}
;
}
{
(, (PAGE_ID) << BITS);
}
};



