Eu me apóio muito em seqüências de caracteres internas, como sugere Basile, onde uma pesquisa de sequência se traduz em um índice de 32 bits para armazenar e comparar. Isso é útil no meu caso, já que às vezes tenho centenas de milhares a milhões de componentes com uma propriedade chamada "x", por exemplo, que ainda precisa ser um nome de seqüência de caracteres fácil de usar, pois geralmente é acessado por scripts por nome.
Eu uso um trie para a pesquisa (experimentei também com, unordered_map
mas meu trie sintonizado, apoiado por pools de memória, pelo menos começou com um desempenho melhor e também foi mais fácil tornar o thread-safe sem bloquear apenas toda vez que a estrutura foi acessada), mas não é tão rápido para construção como criação std::string
. O ponto é mais para acelerar as operações subseqüentes, como verificar a igualdade de strings, que, no meu caso, se resume apenas a verificar a igualdade de dois números inteiros e reduzir drasticamente o uso de memória.
Eu acho que uma opção seria manter algum tipo de registro de valores já alocados, mas é possível fazer pesquisas de registro mais rapidamente do que alocações de memória redundantes?
Vai ser difícil fazer uma pesquisa através de uma estrutura de dados muito mais rápido do que um único malloc
, por exemplo, se você tem um caso em que está lendo um monte de strings de uma entrada externa como um arquivo, por exemplo, minha tentação seria usar um alocador seqüencial, se possível. Isso vem com a desvantagem de que você não pode liberar memória de uma sequência individual. Toda a memória reunida pelo alocador deve ser liberada de uma só vez ou não. Mas um alocador seqüencial pode ser útil nos casos em que você apenas precisa alocar uma carga de pequenos pedaços de memória de tamanho variável de maneira sequencial direta, apenas para depois jogar tudo fora. Não sei se isso se aplica ao seu caso ou não, mas quando aplicável, pode ser uma maneira fácil de corrigir um ponto de acesso relacionado a alocações freqüentes de memória pequenina (que podem ter mais a ver com falhas de cache e falhas de página do que as subjacentes algoritmo usado por, digamos malloc
)).
As alocações de tamanho fixo são mais fáceis de acelerar, sem as restrições sequenciais do alocador que impedem que você libere pedaços específicos de memória para reutilização posterior. Mas tornar a alocação de tamanho variável mais rápida que o alocador padrão é bastante difícil. Basicamente, criar qualquer tipo de alocador de memória mais rápido do que malloc
geralmente é extremamente difícil, se você não aplicar restrições que restrinjam sua aplicabilidade. Uma solução é usar um alocador de tamanho fixo para, digamos, todas as cadeias de caracteres com 8 bytes ou menos, se você tiver uma carga de barco delas e cadeias mais longas forem um caso raro (para o qual você pode apenas usar o alocador padrão). Isso significa que 7 bytes são desperdiçados para cadeias de caracteres de 1 byte, mas devem eliminar pontos de acesso relacionados à alocação, se, digamos, 95% do tempo, suas cadeias de caracteres são muito curtas.
Outra solução que me ocorreu é usar listas vinculadas não desenroladas que podem parecer loucuras, mas que me escutam.
A idéia aqui é tornar cada nó desenrolado um tamanho fixo em vez de tamanho variável. Ao fazer isso, você pode usar um alocador de bloco de tamanho fixo extremamente rápido que agrupa memória, alocando blocos de tamanho fixo para cadeias de tamanho variável vinculadas. Isso não reduzirá o uso de memória, tenderá a aumentar por causa do custo dos links, mas você pode jogar com o tamanho não enrolado para encontrar um equilíbrio adequado às suas necessidades. É uma idéia meio maluca, mas deve eliminar os pontos ativos relacionados à memória, pois agora você pode agrupar efetivamente a memória já alocada em blocos contíguos e volumosos e ainda ter os benefícios de liberar seqüências individualmente. Aqui está um simples e antigo alocador fixo que escrevi (um ilustrativo que criei para outra pessoa, desprovido de problemas relacionados à produção) que você pode usar livremente:
#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;
}