是否有任何可移植的方法可以用类似STL的分配器包装来代替malloc()/free()的使用?
上下文:我有一个C库,它允许为内存管理指定类似malloc()/free()的自定义函数,并用于多线程上下文。四处寻找一个好的多线程分配器,我发现GCC libstdc++的mt_alloc对我的工作负载表现非常好。现在我想在上述C库中使用它,但如何做到呢?
我看到的主要问题是deallocate()函数,与free()相反,它除了获取地址外,还获取分配的内存块的大小。因此,我需要以某种方式跟踪与每个内存分配相关的大小,以便在释放内存时将其反馈给deallocate()。我想解决这个问题的最简单的解决方案是将分配的内存大小存储在内存块的开头,但我不确定如何解决可能出现的对齐问题。
有什么简单的解决方案我忽略了吗?
在我的平台上,malloc
确保分配的内存在8字节的边界上对齐。要模拟这种行为,请使用allocator<uint64_t>
:
#include <stdint.h>
#include <ext/mt_allocator.h>
static __gnu_cxx::__mt_alloc<uint64_t> theAllocator;
void* mtmalloc(size_t size)
{
// Divide size by sizeof(uint64_t) and round up
size_t payloadElementCount = (size + sizeof(uint64_t) - 1) /
sizeof(uint64_t);
// Add an extra uint64_t to store the chunk size
size_t chunkElementCount = 1 + payloadElementCount;
// Allocate the chunk
uint64_t* chunk = theAllocator.allocate(chunkElementCount);
// Store the chunk size in the first word
chunk[0] = chunkElementCount;
// Return a pointer past where the chunk size is stored
return static_cast<void*>(chunk + 1);
}
void mtfree(void* pointer)
{
// The chunk begins one word before the passed in pointer
uint64_t* chunk = static_cast<uint64_t*>(pointer) - 1;
// Retrieve the chunk size
size_t chunkElementCount = chunk[0];
// Deallocate the chunk
theAllocator.deallocate(chunk, chunkElementCount);
}
int main()
{
int* array = (int*)mtmalloc(sizeof(int) * 4);
array[0] = 0;
array[1] = 1;
array[2] = 2;
array[3] = 3;
mtfree(array);
}
对于您的平台,请使用适当的类型替换uint64_t
。
你应该用类似Valgrind的东西来测试这个,以确保没有内存泄漏!
对于可移植到GCC编译器的解决方案,您可以使用GCC的__BIGGEST_ALIGNMENT__
和Boost的aligned_storage
类型特性来代替uint64_t
:
typedef boost::aligned_storage<__BIGGEST_ALIGNMENT__, __BIGGEST_ALIGNMENT__> AlignedType;
Paul Laska在altdevblogaday上写了一个关于这方面的精彩系列。这是第一篇文章的链接:http://altdevblogaday.org/2011/04/11/ready-set-allocate-part-1/
在这篇文章中,他关心块大小分配和对齐问题。它应该为处理您的交易分配问题提供一个经过深思熟虑和精心编写的解决方案。
请参阅我关于在块开头存储值的回答。您可以根据自己的需要对其进行轻微修改。
据我所知,跟踪对象大小的两种主要方法是在一个大小隔离的分配器中隐式地将元数据放在一边(例如Kingsley风格的分配器),或者将对象前面的大小作为对象头(例如dlmalloc)。第三个非常糟糕的解决方案是维护每个分配对象及其大小的映射。当然,这个映射将由另一个分配器来管理。
我认为你走在了正确的轨道上,很高兴你意识到了对齐的考虑因素。我试图在mt_alloc上查找一些信息,看看是否有其他选择或惊喜,但这些信息似乎并不容易获得。一些分配器有一种查询对象大小的方法(这样做可能很便宜,也可能不便宜)。如果deallocate函数需要显式传递大小,那么我猜不存在这样的函数,但你永远不会知道。
如果对齐很重要,则需要对计算进行一些调整,因为分配器可能不会为您返回适当对齐的内存。如果你对返回指针的对齐一无所知,你需要这样的东西:
struct object_header {
size_t size;
};
void * buf = xxmalloc (2 * alignment + size + sizeof(object_header));
void * alignedPtr = (void *) (((size_t) buf + sizeof(object_header) + alignment - 1) & ~(alignment - 1));
如果mt_alloc不能容忍释放内部指针上的对象,那么这种方案会给您带来问题,因为通过填充额外的对齐空间,您将不再知道返回给您的原始地址。在这种情况下,您可能需要在页眉中存储一个额外的字段。
根据mt_alloc内部管理内存的方式,附加一个额外的头也会给您带来很大的开销。在一个大小分离的分配器中,在页面大小的对象上加上这个头可以为您提供高达2倍的空间开销,此时您可以为每个对象额外支付一个页面的成本。在其他分配器中,这可能不是问题,但需要注意。