上面我們在send
的函數(shù)實(shí)現(xiàn)中創(chuàng)建了一個(gè)msg,它的內(nèi)存是在函數(shù)棧空間上臨時(shí)申請的仰迁。一般系統(tǒng)間發(fā)送的消息可能會(huì)比較大箕般,我們知道為了避免棧溢出耐薯,函數(shù)內(nèi)不宜直接定義內(nèi)存占用過大的臨時(shí)變量,所以我們設(shè)計(jì)了一個(gè)內(nèi)存池丝里,專門用于消息的內(nèi)存分配和回收曲初。
auto* msg = PoolAllocator::alloc<BigMsg>();
build(*msg);
// ...
PoolAllocator::free(msg);
PoolAllocator
是一個(gè)全局的內(nèi)存池,它根據(jù)接收的Msg
類型杯聚,計(jì)算得到需要的內(nèi)存塊大小臼婆,在內(nèi)存池中找到合適的內(nèi)存并以Msg*
的類型返回,因此接收內(nèi)存的指針在定義時(shí)可以使用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)存問題纷捞,但不是沒有代價(jià)的痢虹!內(nèi)存池分配的時(shí)候需要在一堆內(nèi)存塊中查找合適的內(nèi)存塊,無論如何是沒有在棧上直接分配效率高的主儡。如果系統(tǒng)中同時(shí)存在大量的小消息奖唯,統(tǒng)一使用內(nèi)存池分配就顯得非常不高效了!
由于dates是框架糜值,它實(shí)現(xiàn)時(shí)不能預(yù)知客戶發(fā)送的消息大小情況丰捷,所以我們必須有一種機(jī)制來讓代碼根據(jù)客戶使用的消息大小自動(dòng)決定在哪里分配內(nèi)存。
現(xiàn)在我們先實(shí)現(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
。在做此事之前還遺留一個(gè)問題骄瓣,那就是StackAllocator
的使用需要生成對象停巷,而PoolAllocator
不用。而且StackAllocator
申請的內(nèi)存會(huì)隨著函數(shù)退出自動(dòng)釋放榕栏,而PoolAllocator
申請的內(nèi)存必須手動(dòng)釋放畔勤。對于此問題,我們再增加一個(gè)中間層扒磁,用于統(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
進(jìn)行了封裝妨托,并且使用了RAII(Resource Acquisition Is Initialization)技術(shù)缸榛,在對象析構(gòu)時(shí)自動(dòng)釋放內(nèi)存吝羞。如此它的用法就和StackAllocator
一致了。
現(xiàn)在如果假定認(rèn)為小于1024字節(jié)的算是小消息内颗,那么我們定義如下MsgAllocator
來自動(dòng)進(jìn)行分配器的類型選擇:
template<typename Msg>
using MsgAllocator = __if(__bool(sizeof(Msg) < 1024), StackAllocator<Msg>, PoolAllocatorWrapper<Msg>);
現(xiàn)在dates中FakeSystem
的send
接口的實(shí)現(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ù)都很短小,所以編譯器基本都會(huì)將其內(nèi)聯(lián)進(jìn)來起暮,因此這里沒有任何的額外開銷卖氨。一切都很酷会烙,不是嗎负懦?!0啬濉V嚼鳌!
對于MsgAllocator
五嫂,我們還可以讓它更通用颗品,將判斷消息大小的值也作為模板參數(shù):
template<typename Msg, int BigSize = 1024>
using MsgAllocator = __if(__bool(sizeof(Msg) < BigSize), StackAllocator<Msg>, PoolAllocatorWrapper<Msg>);