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

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

发表评论

电子邮件地址不会被公开。 必填项已用*标注