上面我们在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>);