zoukankan      html  css  js  c++  java
  • leveldb Arena源码分析

    前言

    对于一个高性能的服务器程序来说,内存的使用非常重要。C++提供new/delete来管理内存的申请和释放,但是对于小对象来说,直接使用new/delete代价比较大,要付出额外的空间和时间,性价比不高。另外,也需要避免多次申请和释放引起的内存碎片。一旦碎片到达一定程度,即使剩余内存足够用,但由于缺乏足够的连续的空闲空间,导致内存不够用的假象。

    C++ STL为避免内存碎片实现了一个复杂的内存池,leveldb则没有那么复杂,只是实现了一个“一次性”内存池Arena。leveldb并不是所有地方都使用了该内存池,主要是memtable使用,主要是用于临时存放用户的更新数据,由于更小的数据可能较小,所以这里使用内存池就很合适。

    原理

    为避免小对象的频繁分配,需要减少对new的调用,最简单的做法就是申请大块的内存,多次分给客户。leveldb使用一个vector<char*>来保存所有内存分配记录表,默认每次申请4k的内存,记录下剩余指针和剩余内存字节数。每当有新的申请,如果当前剩余的字节能满足需求,则直接返回给用户,否则,对于超过1k的申请,直接new返回,小于1k的申请,则申请一个新的4k块,从中分配一部分给用户。

    但这样存在一个问题当前块剩余部分就浪费了,改进的方法,针对每个block都记录剩余字节数,但如此需要遍历查找合适的block,要付出一定性能的代价。谷歌的做法是浪费就浪费吧。至于释放就需要释放整个内存池来释放所占的内存,这个和leveldb的需求有关,memtable不需要释放单次内存,flush到硬盘后整个memtable销毁。

    源码实现

    arena.h

    #ifndef STORAGE_LEVELDB_UTIL_ARENA_H_
    #define STORAGE_LEVELDB_UTIL_ARENA_H_
    
    #include <atomic>
    #include <cassert>
    #include <cstddef>
    #include <cstdint>
    #include <vector>
    
    namespace leveldb {
    
    class Arena {
    public:
        Arena();
        
        Arena(const Arena&) = delete;
        Arena& operator=(const Arena&) = delete;
    
        ~Arena();
    
        // Return a pointer to a newly allocated memory block of "bytes" bytes. 
        char* Allocate(size_t bytes);
    
        // Allocate memory with the normal alignment guarantees provided by malloc.
        char* AllocateAligned(size_t bytes);
    
        // Returns an estimate of the total memory usage of data allocated
        // by the arena.
        size_t MemoryUsage() const {
            return memory_usage_.load(std::memory_order_relaxed);
        }
    
    private:
        char* AllocateFallback(size_t bytes);
        char* AllocateNewBlock(size_t block_bytes);
    
        char* alloc_ptr_;               
        size_t alloc_bytes_remaining_;
        std::vector<char*> block_;
        std::atomic<size_t> memory_usage_;
    };
    
    inline char* Arena::Allocate(size_t bytes) {
        assert(bytes > 0);
        if (bytes <= alloc_bytes_remaining_) {
            char* result = alloc_ptr_;
            alloc_ptr_ += bytes;
            alloc_bytes_remaining_ -= bytes;
            return result;
        }
        return AllocateFallback(bytes);
    }
    
    }
    
    #endif

    arena.cc

    #include "arena.h"
    
    namespace leveldb {
    
    static const int kBlockSize = 4096;
    
    Arena::Arena()
        : alloc_ptr_(nullptr), alloc_bytes_remaining_(0), memory_usage_(0){}
    
    Arena::~Arena() {
        for (size_t i = 0; i < block_.size(); i++) {
            delete[] block_[i];
        }
    }
    
    char* Arena::AllocateFallback(size_t bytes) {
        if (bytes > kBlockSize / 4) {
            char* result = AllocateNewBlock(bytes);
            return result;
        }
    
        alloc_ptr_ = AllocateNewBlock(kBlockSize);
        alloc_bytes_remaining_ = kBlockSize;
    
        char* result = alloc_ptr_;
        alloc_ptr_ += bytes;
        alloc_bytes_remaining_ -= bytes;
        return result;
    }
    
    char* Arena::AllocateAligned(size_t bytes) {
        const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;
        static_assert((align & (align - 1)) == 0,
                       "Pointer size should be a power of 2");
        size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align - 1);
        size_t slop = (current_mod == 0 ? 0 : align - current_mod);
        size_t needed = bytes + slop;
        char* result;
        if (needed <= alloc_bytes_remaining_) {
            result = alloc_ptr_ + slop;
            alloc_ptr_ += needed;
            alloc_bytes_remaining_ -= needed;
        } else {
            result = AllocateFallback(bytes);
        }
        assert((reinterpret_cast<uintptr_t>(result) & (align - 1)) == 0);
        return result;
    }
    
    char* Arena::AllocateNewBlock(size_t block_bytes) {
        char* result = new char[block_bytes];
        block_.push_back(result);
        memory_usage_.fetch_add(block_bytes + sizeof(char*),
                                std::memory_order_relaxed);
        return result;
    }
    
    }

    参考博客:https://www.cnblogs.com/shenzhaohai1989/p/3904808.html

  • 相关阅读:
    Python 缓冲区
    Python接收执行参数
    Python编码
    Maven 多环境 打包
    JS 时间 获取 当天,昨日,本周,上周,本月,上月
    Window Mysql 5.7.18安装
    Eclipse 更改Maven项目名
    Redis 命令
    Redis 安装 和 启动
    Mongodb 安装 和 启动
  • 原文地址:https://www.cnblogs.com/evenleee/p/11295547.html
Copyright © 2011-2022 走看看