【C++內存管理】15_Loki allocator 源碼分析

Loki是由 Andrei 編寫的一個與《Modern C++ Design》(C++設計新思惟)一書配套發行的C++代碼庫。其中有兩個文件 SmallObj.hSmallObj.cpp 進行內存管理,能夠單獨進行使用算法

Loki 源碼下載數組

類層次結構

SmallObj 文件中有三個類:chunk, FixedAllocatorSmallObjAllocator。其中SmallObjAllocator 位於最頂層供應用程序調用 cookie

image.png

Chunk

Chunk 是類層次結構中最底層管理內存塊的類,它負責向操做系統進行內存申請oop

Init, Reset, Release

image.png

1. Init(), 使用 operator new 申請一段內存 chunk, 並使用 pData_ 指向 chunk
2. Reset(), 對 pData_ 指向的內存進行分割。[數組代替鏈表,索引代替指針]
            [與嵌入式指針相似]每一塊 block 的第一個字節存放的是下一個可用的 block 距離起始位置 pData_ 的偏移量(以 block 大小爲單位)
3. Relese(), 向操做系統歸還內存
--
1. blockSize、blocksblock, block 大小及數量            
2. firstAvailableBlock_,當前可用內存塊的偏移量
3. blocksAvailable,當前 chunk 中剩餘的 block 數量
unsigned char i = 0;
unsigned char *p = pData;
for(;i!=blocks; p+=blockSize)  // 以 blockSize 爲間隔切分 chunk 爲 block
    *p = ++i;                  // 以 block 的第一個字節存儲下一個可用 block 索引
參數初始化後的 chunk

image.png

Allocate

用索引對區塊進行管理[第一字節流水號]

image.png

Deallocate

image.png

FixedAllocator

FixedAllocate 負責管理一個具備相同大小 block 的 chunk 集合。它負責根據應用程序需求,建立特定大小的 chunk, 並放置在 vcector 中進行管理spa

Allocate

void *FixedAllocator::allocate()
{
    if (allocChunk_ == 0 || allocChunk_->blocksAvailable == 0)
    {
        // 目前沒有標定 chunk 或 該 chunk 已無可用區塊
        
        Chunks::iterator i = chunks_.begin();           // 打算從頭找起
        for (;; ++i)                                    // 找遍每一個 chunk 直至找到擁有可用區塊者
        {
            if (i == chunks_.end())                     // 到達尾端,都沒找到
            {
                // Initialize
                chunks_.push_back(Chunk());             // 產生 a new chunk 掛於末端; Chunk(),建立臨時對象拷貝至容器而後結束生命 
                Chunk& newChunk = chunks_.back();       // 指向末端
                newChunk.Init(blockSize_, numBlocks_);  // 設置好索引
                allocChunk_ = &newChunk;                // 標定,稍後將對此 chunk 取區塊
                deallocChunk_ = &chunks_.front();       // 另外一標定
                break;
            }
            
            if (i->blocksAvailable_ > 0)
            {
                // current chunk 有可用區塊
                allocChunk_ = &*i;  // 取地址
                break;
            }
        }
    }
    
    // allocChunk_, 在此 chunk 找到可用區塊,下次就優先今後找起
    return allocChunk_->Allocate(blockSize_); // 向這個 chunk 取區塊 
}
allocChunk_
標記最近一次知足分配動做的 chunk, 當下次再有分配需求時,優先檢查此 chunk
deallochunk_
依靠數據的內聚性和區域性原則
當某一 chunk 發生內存回收時,下次回收也可能發生在此 chunk 上。
以此儘可能避免 `void Deallocate(void *p)`中 p 落在哪個 chunks 的遍歷查找動做(類比於上述代碼 for )
deallocChunk_ = &chunks_.front()
vector 在進行 insert 時,可能會致使內存增加,內存增加時元素將從舊空間拷貝到新的空間,此時過去 deallocChunk_ 指向的地址將失效,所以須要對 deallocChunk_ 從新標定

Deallocate

咱們須要根據歸還內存的地址,把這塊內存回收到對應的 chunk 中操作系統

void FixedAllocator::Deallocate(void *p)
{
    deallocChunk = VicinityFind(p);
    DoDeallocate();
}

VicinityFind

根據內存使用的區域性,採用臨近查找法肯定 p 所對應的 chunk .net

image.png

1. 已知每一塊 chunk 指向內存空間的地址 p_Data_
2. 已知每一塊內存空間的大小 numblocks_ * blocksize
3. 由此可計算出每一塊 chunk 指向內存的地址範圍 [p_Data_, p_Data_ + numblocks_ * blocksize]
4. 由此可計算出歸還的內存 p 屬於哪個 chunk

---

查找思想:VicinityFind 採用臨近分頭查找的算法,從上一次 dealloChunk_ 的位置出發進行上下兩頭查找
(內存分配一般是個容器服務的,而容器元素連續建立時,一般就從同一個 chunk 得到連續的地址空間,歸還的時候固然也是歸還到同一塊 chunk。經過對上一次歸還 chunk 的記錄,儘可能避免遍歷搜索,提升了查找定位速度)

在上述實現中,若是 p 並不是當初由此係統得到,確定找不到對應的 chunk,因而陷入死循環。在新版本中已修復此問題

DoDeallocate

完成實際的內存回收設計

image.png

1. deallocChunk->Deallocate(p, blockSize_); 由 FixedAllocator::chunk::Deallocate(void *p, std::size_t blockSize) 完成底層的內存回收
2. 當 deallockChunk_->blocksAvailable_ = numBlocks_ 時表示當前內存能夠歸還給操做系統
3. 延遲歸還機制,把空的 chunk 交換到 vector 尾部,只有出現兩個空的 chunk 時,纔會發生真正的內存歸還動做(表中標註①②③)

SmallObjAllocator

SmallObjAllocator 負責管理具備不一樣 block size 的 FixedAllocate 的vector 集合指針

Allocate

void* SmallObjAllocator::Allocate(std::size_t numBytes)
{
    if (numBytes > maxObjectSize_) return operator new(numBytes);
    
    if (pLastAlloc_ && pLastAlloc_->BlockSize() == numBytes)
    {
        return pLastAlloc_->Allocate();
    }

    //找到第一個 >= numBytes 的位置
    Pool::iterator i = std::lower_bound(pool_.begin(), pool_.end(), numBytes);

    //沒找到相同的,就從新建立一個 FixedAllocator
    if (i == pool_.end() || i->BlockSize() != numBytes)
    {
        i = pool_.insert(i, FixedAllocator(numBytes));
        pLastDealloc_ = &*pool_.begin();
    }
    pLastAlloc_ = &*i;
    return pLastAlloc_->Allocate();
}
1. 當應用程序請求的 numBytes 大於 maxObjectSize_ 時交由 operator new 處理
2. pLastAlloc_ 記錄上次分配 block 的 FixedAllocator object。若是本次申請的 block size 等於上次分配的 block size,就直接使用同一個 FixedAllocator object,以此盡力避免查找動做(最佳客戶是容器,容器的元素大小是相同的)
3. 若是本次需求的 block size 不等於上次分配的 block size,就遍歷查找大小相等的 FixedAllocator object。若是沒有找到,就插入新的 FixedAllocator object。同時爲了不 vector 擴容引發的內存從新分配,對 pLastDealloc_  重定位

Deallocate

void SmallObjAllocator::Deallocate(void* p, std::size_t numBytes)
{
    if (numBytes > maxObjectSize_) return operator delete(p);

    if (pLastDealloc_ && pLastDealloc_->BlockSize() == numBytes)
    {
        pLastDealloc_->Deallocate(p);
        return;
    }
    Pool::iterator i = std::lower_bound(pool_.begin(), pool_.end(), numBytes);
    assert(i != pool_.end());
    assert(i->BlockSize() == numBytes);
    pLastDealloc_ = &*i;
    pLastDealloc_->Deallocate(p);
}

Loki allocator 反省

  • 曾經有兩個 bugs, 新版已修正
  • 精簡強悍;手段暴力(關於 for-loop)
  • 使用「以 array 取代 list, 以 index 取代 pointer」 的特殊實現手法
  • 可以以簡單的方式判斷 「chunk 全回收」 進而將 memory 歸還給操做系統
  • 有 Deferring (延遲歸還)能力
  • 這是個 allocator, 用來分配大量小塊不帶 cookie 的memory blocks, 它的最佳客戶是容器(由於使用時要記錄塊大小)
  • 內部使用的 vector 採用 std::allocator 實現

與 std::alloc 的比較

std::allocator loki::allocator
不會向操做系統歸還內存 延遲機制內存歸還
服務於 8-128(每次增長 8byte) 內存塊,申請不知足時RoundUp調整 爲不大於最大 block size 的全部 block size 服務
相關文章
相關標籤/搜索