1. 程式人生 > >【C++學習】C++實現高效記憶體池

【C++學習】C++實現高效記憶體池

1.記憶體池簡介

記憶體池是池化技術中的一種形式,通常我們在編寫程式的時候使用new和delete關鍵字向作業系統申請記憶體。但是每一個申請記憶體和釋放記憶體的時候,都需要和作業系統的系統呼叫進行互動,並在堆中進行記憶體分配。如果操作過於頻繁,就會出現大量的記憶體碎片進而降低記憶體的分配效能。從而導致記憶體分配失敗的情況。對於記憶體申請過程而言,其實就是一次申請指標的過程,對於每一次的記憶體分配,都會消耗一次分配記憶體的時間,如果在程式開始的時候就分配好一塊合理的記憶體區域,當我們下次使用的時候,就可以直接從分配的記憶體中進行使用,降低申請記憶體分配時作業系統進行復雜排程過程的時間。

優勢:
1.從原理上比new

malloc
2.分配記憶體的時候overhead
3.基本不會出現記憶體碎片
4.無需一個一個釋放記憶體,只需要釋放記憶體池就可以

2.函式實現

2.1 主函式

#include <iostream>
#include <ctime>
#include <vector>
#include <cassert>
#include "StackAlloc.h"
#include "MemoryPool.h"

using namespace std;

const int REPS = 100;
const int ELEMS = 1e8
; int main() { clock_t start; // Use standard allocator StackAlloc<int, std::allocator<int> > stackDefault; start = clock(); for(int reps=0; reps < REPS; ++reps) { assert(stackDefault.empty()); for(int elems=0; elems < ELEMS; ++elems) { stackDefault.push(elems); } for
(int elems=0; elems < ELEMS; ++elems) { stackDefault.pop(); } } std::cout << "Default Allocator Time: "; std::cout << (double)clock() - start / CLOCKS_PER_SEC << "\n\n"; // Use memory pool StackAlloc<int, MemoryPool<int > > stackPool; start = clock(); for(int reps=0; reps < REPS; ++reps) { assert(stackPool.empty()); for(int elems=0; elems < ELEMS; ++elems) { stackPool.push(elems); } for(int elems=0; elems < ELEMS; ++elems) { stackPool.pop(); } } std::cout << "Memory Allocator Time: "; std::cout << (double)clock() - start / CLOCKS_PER_SEC << "\n\n"; return 0; }

多次執行鏈式棧的入棧出棧過程,對時間效能進行比較。分別使用標準庫中的allocator和自己實現的記憶體池進行記憶體測試。對於newdelete,在變數分配記憶體給空間的同時對變數進行初始化,std::allocator將變數初始化和記憶體分配隔離開。

2.2 StackAlloc.h

#ifndef OPTIMIZED_STACKALLOC_H
#define OPTIMIZED_STACKALLOC_H
#include <memory>

template <typename T>
struct StackNode_{
    T data;
    StackNode_* prev;
};

template <typename T, typename Alloc=std::allocator<T > >
class StackAlloc {
public:

    typedef StackNode_<T> node;
    typedef typename Alloc::template rebind<node>::other allocator;

    StackAlloc();

    ~StackAlloc();

    bool empty();

    void clear();

    void push(T element);

    void pop();

    void top();

private:

    allocator allocator_;
    Node* head_;

};
#endif //OPTIMIZED_STACKALLOC_H

1.結構體為資料值和指向上一個結點的指標。
2.template <typename T, typename Alloc=std::allocator<T > >定義一個模板類,預設記憶體分配器是std::allocator
3.typedef typename Alloc::template rebind<node>::other allocator;
(1)typedef ... allocator給記憶體分配器重新設定一個別名。
(2)typename關鍵字是進行類別限定, 對於後面的部分在標準庫中可以被認為是靜態資料成員、靜態成員函式以及巢狀型別。使用該關鍵字說明後面的是一個型別而不是一個成員變數。
(3)rebind如果已經為型別T建立了一個記憶體分配器allocator<T>,如果需要按照相同的策略對另外一個型別U構建一個分配器。那麼可以使用allocator<U> = allocator<T>::rebind<U>::other,這樣如果是我們自己定義的分配器模板allocator,就可以使用該方式,快速地實現多型別的構建過程(同族)。因為在構建T的分配器的的時候,是一個模板方式進行定義的,且rebind依賴於allocator,所以也要宣告為模板。

2.3 StackAlloc.cpp

2.4 禁用拷貝賦值

為降低程式代價,禁用拷貝賦值,僅使用移動賦值,因為記憶體池拷貝的代價巨大。

MemoryPool& operator=(const MemoryPool& mp) = delete;
MemoryPool& operator=(const MemoryPool& mp) noexcept;

C++11中的delect表示對前面的函式宣告進行禁用。

2.5 靜態斷言與斷言

斷言方式:assert是在執行時進行斷言,而static_assert是在編譯階段進行斷言。

2.6 使用reinterpret_cast

在對記憶體池進行刪除的時候,通過對delete進行過載,並使用reinterpret_cast將指標強制轉換為void *型別。