相比leveldb,rocksdb对内存块的分配主要做了两点改进:一是抽象出内存分配Allocator,支持对不同内存管理策略进行定制扩展;二是启用HugePage支持,提高大内存机器下内存分配和访问的性能。
1. 对内存池的管理维护进行了进一步抽象,以支持定制扩展。
- class Allocator {
- public:
- virtual ~Allocator() {}
- virtual char* Allocate(size_t bytes) = 0;
- virtual char* AllocateAligned(size_t bytes, size_t huge_page_size = 0,
- Logger* logger = nullptr) = 0;
- virtual size_t BlockSize() const = 0;
- };
Arena继承自Allocator实现了基础接口,并进行定制改进。
2. Arena一些细节上的改进优化以及对HugePage支持。
1> 对于常规内存分配,对齐和非对齐版本,不再混用,同一块内存block仅支持一种分配方式。
2> 内存块基本单元可定制,leveldb版本即kBlockSize为4K不可改变,而rocksdb实现可通过OptimizeBlockSize计算最佳大小。
- const size_t Arena::kMinBlockSize = 4096;
- const size_t Arena::kMaxBlockSize = 2u << 30;
- static const int kAlignUnit = sizeof(void*);
- size_t OptimizeBlockSize(size_t block_size) {
- // Make sure block_size is in optimal range
- block_size = std::max(Arena::kMinBlockSize, block_size);
- block_size = std::min(Arena::kMaxBlockSize, block_size);
- // make sure block_size is the multiple of kAlignUnit
- if (block_size % kAlignUnit != 0) {
- block_size = (1 + block_size / kAlignUnit) * kAlignUnit;
- }
- return block_size;
- }
3> 启用HugePage支持,重点来看这块的优化。
通常我们使用的linux系统pagesize默认都是4096,HugePage即大页面的支持,以Centos linux 3.10 X86-64为例,Hugepage默认大小为2MB(可通过cat /proc/meminfo | grep Huge获取)。
linux系统内存管理采用的是段页式,分页使用页表PageTable和TLB缓存,大内存下使用通常默认的4K页面会使TLB条目过多,影响CPU寻址效率,因此使用Hugepage增加页面尺寸可以减轻TLB压力,减少TLB miss,同样也可以减轻PageTable的负载。
另一方面,HugePages不会swap,不存在因内存不足引发的换入换出问题。
根据RocksDB官方wiki,考虑到数据局部性及缓存友好性,建议对indexes和BloomFilters的内存分配启用HugePage。
至于linux下HugePage的配置,可以参考引文。
在具体使用上,HugePage的内存分配通过mmap调用完成。
4> 细节处理及对性能的极致优化,如AllocateFromHugePage为防止内存泄漏,预先对vector内存池大小进行reserve;为提高对vector的插入效率,使用emplace_back(c++11支持)而不是常用的push_back。
- char* Arena::AllocateFromHugePage(size_t bytes) {
- #ifdef MAP_HUGETLB
- if (hugetlb_size_ == 0) {
- return nullptr;
- }
- // already reserve space in huge_blocks_ before calling mmap().
- // this way the insertion into the vector below will not throw and we
- // won't leak the mapping in that case. if reserve() throws, we
- // won't leak either
- huge_blocks_.reserve(huge_blocks_.size() + 1);
- void* addr = mmap(nullptr, bytes, (PROT_READ | PROT_WRITE),
- (MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB), -1, 0);
- if (addr == MAP_FAILED) {
- return nullptr;
- }
- // the following shouldn't throw because of the above reserve()
- huge_blocks_.emplace_back(MmapInfo(addr, bytes));
- blocks_memory_ += bytes;
- return reinterpret_cast<char*>(addr);
- #else
- return nullptr;
- #endif
- }
Refer:
1. http://blog.csdn.net/leshami/article/details/8777639
2. https://github.com/facebook/rocksdb/wiki/Allocating-Some-Indexes-and-Bloom-Filters-using-Huge-Page-TLB