本文将分析levelDB 中記憶體管理類Arena 的實作,通過分析該類的實作,我學到了如何封裝記憶體的配置設定操作(通過統一的接口來配置設定不同大小的記憶體,而不用考慮記憶體釋放),以及如何預先配置設定一整塊記憶體來解決頻繁配置設定小塊記憶體浪費時間,直接配置設定大塊記憶體浪費記憶體的問題,還學到了如何保證記憶體對齊。真好。
Arena每次按kBlockSize(
static const int kBlockSize = 4096;
)機關向系統申請記憶體,提供位址對齊的記憶體,記錄記憶體使用。當memtable 申請記憶體時,如果size不大于kBlockSize的四分之一,就在目前空閑的記憶體block 中配置設定,否則,直接向系統申請。這個政策是為了更好的服務小記憶體的申請,避免個别大記憶體使用影響。
Arena類的聲明如下:
class Arena {
public:
Arena();
~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 (including space allocated but not yet used for user
// allocations).
size_t MemoryUsage() const {
return blocks_memory_ + blocks_.capacity() * sizeof(char*);
}
private:
char* AllocateFallback(size_t bytes);
char* AllocateNewBlock(size_t block_bytes);
// Allocation state
char* alloc_ptr_;
size_t alloc_bytes_remaining_;
// Array of new[] allocated memory blocks
std::vector<char*> blocks_;
// Bytes of memory in blocks allocated so far
size_t blocks_memory_;
// No copying allowed
Arena(const Arena&);
void operator=(const Arena&);
};
上面就是Arena類的申明,可以看到,除了構造函數和析構函數,隻有3個公有的成員函數,也就是說,使用者隻能使用這3個函數(Allocate, allocatedAligned, MemoryUsage()),此外,Arena 類還有兩個私有的成員函數和4個成員變量,我們分别來看看它們的用法。
Arena的關鍵成員函數就是一個
vector<char*> blocks_
向量,該向量存放若幹個指針,每個指針指向一塊記憶體,結構圖如下:
成員變量blocks_memory用于記錄記憶體的使用情況,是以,MemoryUsage() 函數的定義如下:
// Returns an estimate of the total memory usage of data allocated
// by the arena (including space allocated but not yet used for user
// allocations).
size_t MemoryUsage() const {
return blocks_memory_ + blocks_.capacity() * sizeof(char*);
}
我們先不管AllocateAligned()函數,那麼,Arena的使用者可以調用的成員函數隻有Allocate()了,我們來看看它是怎麼定義的:
inline char* Arena::Allocate(size_t bytes) {
// The semantics of what to return are a bit messy if we allow
// 0-byte allocations, so we disallow them here (we don't need
// them for our internal use).
assert(bytes > 0);
if (bytes <= alloc_bytes_remaining_) {
char* result = alloc_ptr_;
alloc_ptr_ += bytes;
alloc_bytes_remaining_ -= bytes;
return result;
}
return AllocateFallback(bytes);
}
該函數傳回bytes位元組的記憶體,如果預先配置設定的記憶體(
alloc_bytes_remaining_
)大于bytes,也就是說,預先配置設定的記憶體能夠滿足現在的需求,那就傳回預先配置設定的記憶體,而不用向系統申請,如果(
alloc_bytes_remaining_ < bytes
),也就是說,預先配置設定的記憶體不能不夠用,那就調用AllocateFallback來配置設定記憶體,AllocateFallback的實作如下:
char* Arena::AllocateFallback(size_t bytes) {
if (bytes > kBlockSize / 4) {
// Object is more than a quarter of our block size. Allocate it separately
// to avoid wasting too much space in leftover bytes.
char* result = AllocateNewBlock(bytes);
return result;
}
// We waste the remaining space in the current block.
alloc_ptr_ = AllocateNewBlock(kBlockSize);
alloc_bytes_remaining_ = kBlockSize;
char* result = alloc_ptr_;
alloc_ptr_ += bytes;
alloc_bytes_remaining_ -= bytes;
return result;
}
可以看到,AllocateFallback裡有兩種情況,一種是
bytes > kBlockSize / 4
的情況,另一種是
bytes <= kBlockSize / 4
的情況,對這兩種情況的處理是不一樣的。兩種情況都會調用AllocateNewBlock()函數,我們先來看一下它到底做了什麼:
char* Arena::AllocateNewBlock(size_t block_bytes) {
char* result = new char[block_bytes];
blocks_memory_ += block_bytes;
blocks_.push_back(result);
return result;
}
這個函數隻是調用new配置設定記憶體,然後将指針存放到
vector<char *> blocks_
容器中,并記錄到目前為止共配置設定的記憶體(
blocks_memory_ += block_bytes
)的位元組數,我們再來看AllocateFallback 中的兩種情況:
第一種情況,直接調用AllocateNewBlock 配置設定bytes 位元組的記憶體,沒有多配置設定,第二種情況調用AllocateNewBlock 配置設定kBlockSize位元組的記憶體,然後保留未使用的部分,留着下次使用,這裡需要注意:
在Allocate()中,對于
bytes > alloc_bytes_remaining_
的情況,我們需要調用AllocateFallback ,但是,在AllocateFallback 中的
bytes < kBlockSize / 4
的情況,我們直接配置設定了一塊,令alloc_ptr(該指針指向預先配置設定,但未使用的記憶體)指向新配置設定的塊,也就是說,有一部分記憶體(
alloc_bytes_remaining_
位元組)被我們浪費了。
下面再來看看Arena的另一個成員函數AllocateAligned(),看它是如何保證配置設定的記憶體是對齊的,AllocateAligned函數的定義如下:
char* Arena::AllocateAligned(size_t bytes) {
const int align = sizeof(void*); // We'll align to pointer size
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 {
// AllocateFallback always returned aligned memory
result = AllocateFallback(bytes);
}
assert((reinterpret_cast<uintptr_t>(result) & (align-1)) == 0);
return result;
}
首先擷取一個指針的大小
const int align = sizeof(void*)
,很明顯,在32位系統下是4 ,64位系統下是8 ,為了表述友善,我們假設是32位系統,即align = 4, 然後将我們使用的char * 指針位址轉換為一個無符号整型(reinterpret_cast<uintptr_t>(result):It is an unsigned int that is guaranteed to be the same size as a pointer.),通過與操作來擷取
size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align-1);
目前指針模4的值,有了這個值以後,我們就容易知道,還差
slop = align - current_mod
多個位元組,記憶體才是對其的,是以有了
result = alloc_ptr + slop
最後再看一下Arena的析構函數和構造函數:
Arena::Arena() {
blocks_memory_ = 0;
alloc_ptr_ = NULL; // First allocation will allocate a block
alloc_bytes_remaining_ = 0;
}
Arena::~Arena() {
for (size_t i = 0; i < blocks_.size(); i++) {
delete[] blocks_[i];
}
}