上面我們在send
的函數實現中創建了一個msg,它的內存是在函數棧空間上臨時申請的。一般系統間發送的消息可能會比較大,我們知道為了避免棧溢出,函數內不宜直接定義內存占用過大的臨時變量,所以我們設計了一個內存池,專門用于消息的內存分配和回收。
auto* msg = PoolAllocator::alloc<BigMsg>();
build(*msg);
// ...
PoolAllocator::free(msg);
PoolAllocator
是一個全局的內存池,它根據接收的Msg
類型,計算得到需要的內存塊大小,在內存池中找到合適的內存并以Msg*
的類型返回,因此接收內存的指針在定義時可以使用auto
關鍵字。PoolAllocator
中alloc
和free
函數的原型如下:
struct PoolAllocator
{
template<typename Msg> static Msg* alloc();
static void free(void* msg);
};
使用內存池解決了在函數棧上申請過大內存問題,但不是沒有代價的!內存池分配的時候需要在一堆內存塊中查找合適的內存塊,無論如何是沒有在棧上直接分配效率高的。如果系統中同時存在大量的小消息,統一使用內存池分配就顯得非常不高效了!
由于dates是框架,它實現時不能預知客戶發送的消息大小情況,所以我們必須有一種機制來讓代碼根據客戶使用的消息大小自動決定在哪里分配內存。
現在我們先實現一種可以在棧上分配內存的分配器:
template<typename Msg>
struct StackAllocator
{
Msg* alloc()
{
return &msg;
}
private:
Msg msg;
};
StackAllocator
需要如下使用:
StackAllocator<BigMsg> allocator;
auto* msg = allocator.alloc();
build(*msg);
現在我們就可以根據Msg的大小來做類型選擇,如果消息大則用PoolAllocator
,否則用StackAllocator
。在做此事之前還遺留一個問題,那就是StackAllocator
的使用需要生成對象,而PoolAllocator
不用。而且StackAllocator
申請的內存會隨著函數退出自動釋放,而PoolAllocator
申請的內存必須手動釋放。對于此問題,我們再增加一個中間層,用于統一兩者的使用方式。
template<typename Msg>
struct PoolAllocatorWrapper
{
Msg* alloc()
{
msg = PoolAllocator::alloc<Msg>();
return msg;
}
~PoolAllocatorWrapper()
{
PoolAllocator::free(msg);
msg = nullptr;
}
private:
Msg* msg{nullptr};
};
PoolAllocatorWrapper
對PoolAllocator
進行了封裝,并且使用了RAII(Resource Acquisition Is Initialization)技術,在對象析構時自動釋放內存。如此它的用法就和StackAllocator
一致了。
現在如果假定認為小于1024字節的算是小消息,那么我們定義如下MsgAllocator
來自動進行分配器的類型選擇:
template<typename Msg>
using MsgAllocator = __if(__bool(sizeof(Msg) < 1024), StackAllocator<Msg>, PoolAllocatorWrapper<Msg>);
現在dates中FakeSystem
的send
接口的實現可以修改如下了:
template<typename BUILDER>
void send(const BUILDER& builder)
{
using Msg = __lambda_para(BUILDER, 0);
MsgAllocator<Msg> allocator;
auto msg = allocator.alloc();
builder(*msg);
// ...
}
由于我們的StackAllocator
和PoolAllocatorWrapper
都定義在頭文件中,而且成員函數都很短小,所以編譯器基本都會將其內聯進來,因此這里沒有任何的額外開銷。一切都很酷,不是嗎?!!!!
對于MsgAllocator
,我們還可以讓它更通用,將判斷消息大小的值也作為模板參數:
template<typename Msg, int BigSize = 1024>
using MsgAllocator = __if(__bool(sizeof(Msg) < BigSize), StackAllocator<Msg>, PoolAllocatorWrapper<Msg>);