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