正如Basile所建议的那样,我在很大程度上依赖于内部字符串,其中字符串查找转换为32位索引以进行存储和比较。在我的情况下,这很有用,因为有时我有成千上万个具有名为“ x”的属性的组件,例如,由于脚本编写者经常按名称访问它,所以仍需要使用用户友好的字符串名称。
我使用trie进行查找(也进行了实验,unordered_map
但由内存池支持的经过调整的trie至少开始表现更好,并且更容易使线程安全,而不必每次访问结构时都锁定),但事实并非如此快速建造创造std::string
。关键是要加快后续操作的速度,例如检查字符串是否相等,就我而言,这归结为检查两个整数是否相等并大幅度减少内存使用量。
我猜一个选择是维护某种已经分配了值的注册表,但是是否有可能使注册表查找比冗余内存分配更快?
要比单个数据结构更快地搜索数据结构将非常困难 malloc
,例如,如果您要从外部输入(例如文件)中读取大量字符串,那么我的诱惑是如果可能的话,使用顺序分配器。缺点是您无法释放单个字符串的内存。分配器池中的所有内存必须立即释放或完全不释放。但是,在只需要以直接的顺序方式分配大量可变大小的微小内存块的情况下,顺序分配器会很方便,之后再将其扔掉。我不知道这是否适用于您的情况,但在适用时,它可能是修复与频繁的小内存分配有关的热点的简便方法(这可能与缓存未命中和页面错误有关,而不是与底层问题有关。的算法malloc
)。
在没有顺序分配器约束的情况下,固定大小的分配更容易加速,因为顺序分配器约束阻止您释放特定的内存块以供以后重用。但是,使可变大小的分配比默认分配器更快是非常困难的。基本上,malloc
如果不应用限制其适用性的约束,则使任何一种内存分配器都比通常非常困难的内存分配器快。一种解决方案是对所有长度等于或小于8字节的字符串使用固定大小的分配器,如果您有很多负载的话,则较长的字符串很少见(您可以使用默认分配器)。这确实意味着1个字节的字符串浪费了7个字节,但是它应该消除与分配相关的热点,例如,如果95%的时间中您的字符串很短。
我刚刚想到的另一种解决方案是使用展开的链表,这听起来可能很疯狂,但是却听到了我的声音。
这里的想法是使每个展开的节点为固定大小而不是可变大小。当您执行此操作时,可以使用非常快速的固定大小的块分配器来池化内存,为链接在一起的可变大小的字符串分配固定大小的块。那不会减少内存的使用,由于链接的成本,它会增加内存的使用量,但是您可以使用展开的大小来找到适合您需求的平衡。这是个古怪的主意,但是应该消除与内存相关的热点,因为您现在可以有效地池化已分配在庞大连续块中的内存,并且仍然具有分别释放字符串的好处。这是我写的一个简单的ol'固定分配器(我为别人制作的一个示例,没有生产相关的绒毛),您可以自由使用:
#ifndef FIXED_ALLOCATOR_HPP
#define FIXED_ALLOCATOR_HPP
class FixedAllocator
{
public:
/// Creates a fixed allocator with the specified type and block size.
explicit FixedAllocator(int type_size, int block_size = 2048);
/// Destroys the allocator.
~FixedAllocator();
/// @return A pointer to a newly allocated chunk.
void* allocate();
/// Frees the specified chunk.
void deallocate(void* mem);
private:
struct Block;
struct FreeElement;
FreeElement* free_element;
Block* head;
int type_size;
int num_block_elements;
};
#endif
#include "FixedAllocator.hpp"
#include <cstdlib>
struct FixedAllocator::FreeElement
{
FreeElement* next_element;
};
struct FixedAllocator::Block
{
Block* next;
char* mem;
};
FixedAllocator::FixedAllocator(int type_size, int block_size): free_element(0), head(0)
{
type_size = type_size > sizeof(FreeElement) ? type_size: sizeof(FreeElement);
num_block_elements = block_size / type_size;
if (num_block_elements == 0)
num_block_elements = 1;
}
FixedAllocator::~FixedAllocator()
{
// Free each block in the list, popping a block until the stack is empty.
while (head)
{
Block* block = head;
head = head->next;
free(block->mem);
free(block);
}
free_element = 0;
}
void* FixedAllocator::allocate()
{
// Common case: just pop free element and return.
if (free_element)
{
void* mem = free_element;
free_element = free_element->next_element;
return mem;
}
// Rare case when we're out of free elements.
// Create new block.
Block* new_block = static_cast<Block*>(malloc(sizeof(Block)));
new_block->mem = malloc(type_size * num_block_elements);
new_block->next = head;
head = new_block;
// Push all but one of the new block's elements to the free stack.
char* mem = new_block->mem;
for (int j=1; j < num_block_elements; ++j)
{
void* ptr = mem + j*type_size;
FreeElement* element = static_cast<FreeElement*>(ptr);
element->next_element = free_element;
free_element = element;
}
return mem;
}
void FixedAllocator::deallocate(void* mem)
{
// Just push a free element to the stack.
FreeElement* element = static_cast<FreeElement*>(mem);
element->next_element = free_element;
free_element = element;
}