Arena内存管理优化-RocksDB源码剖析(0)

相比leveldb,rocksdb对内存块的分配主要做了两点改进:一是抽象出内存分配Allocator,支持对不同内存管理策略进行定制扩展;二是启用HugePage支持,提高大内存机器下内存分配和访问的性能。

1. 对内存池的管理维护进行了进一步抽象,以支持定制扩展。

  1. class Allocator {
  2. public:
  3. virtual ~Allocator() {}
  4.  
  5. virtual char* Allocate(size_t bytes) = 0;
  6. virtual char* AllocateAligned(size_t bytes, size_t huge_page_size = 0,
  7. Logger* logger = nullptr) = 0;
  8.  
  9. virtual size_t BlockSize() const = 0;
  10. };

Arena继承自Allocator实现了基础接口,并进行定制改进。

2. Arena一些细节上的改进优化以及对HugePage支持。
1> 对于常规内存分配,对齐和非对齐版本,不再混用,同一块内存block仅支持一种分配方式。
2> 内存块基本单元可定制,leveldb版本即kBlockSize为4K不可改变,而rocksdb实现可通过OptimizeBlockSize计算最佳大小。

  1. const size_t Arena::kMinBlockSize = 4096;
  2. const size_t Arena::kMaxBlockSize = 2u << 30;
  3. static const int kAlignUnit = sizeof(void*);
  4.  
  5. size_t OptimizeBlockSize(size_t block_size) {
  6. // Make sure block_size is in optimal range
  7. block_size = std::max(Arena::kMinBlockSize, block_size);
  8. block_size = std::min(Arena::kMaxBlockSize, block_size);
  9.  
  10. // make sure block_size is the multiple of kAlignUnit
  11. if (block_size % kAlignUnit != 0) {
  12. block_size = (1 + block_size / kAlignUnit) * kAlignUnit;
  13. }
  14.  
  15. return block_size;
  16. }

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。

  1. char* Arena::AllocateFromHugePage(size_t bytes) {
  2. #ifdef MAP_HUGETLB
  3. if (hugetlb_size_ == 0) {
  4. return nullptr;
  5. }
  6. // already reserve space in huge_blocks_ before calling mmap().
  7. // this way the insertion into the vector below will not throw and we
  8. // won't leak the mapping in that case. if reserve() throws, we
  9. // won't leak either
  10. huge_blocks_.reserve(huge_blocks_.size() + 1);
  11.  
  12. void* addr = mmap(nullptr, bytes, (PROT_READ | PROT_WRITE),
  13. (MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB), -1, 0);
  14.  
  15. if (addr == MAP_FAILED) {
  16. return nullptr;
  17. }
  18. // the following shouldn't throw because of the above reserve()
  19. huge_blocks_.emplace_back(MmapInfo(addr, bytes));
  20. blocks_memory_ += bytes;
  21. return reinterpret_cast<char*>(addr);
  22. #else
  23. return nullptr;
  24. #endif
  25. }

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

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注