相比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