Ich stütze mich stark auf internierte Zeichenfolgen, wie Basile vorschlägt, wo eine Zeichenfolgensuche in einen 32-Bit-Index übersetzt wird, um sie zu speichern und zu vergleichen. Dies ist in meinem Fall nützlich, da ich manchmal Hunderttausende bis Millionen von Komponenten mit einer Eigenschaft namens "x" habe, z. B. die immer noch ein benutzerfreundlicher Zeichenfolgenname sein muss, da Skripter häufig namentlich darauf zugreifen.
Ich verwende einen Trie für die Suche (experimentiert auch mit, unordered_map
aber mein optimierter Trie, der von Speicherpools unterstützt wird, zeigte zumindest eine bessere Leistung und war auch einfacher, Thread-sicher zu machen, ohne jedes Mal, wenn auf die Struktur zugegriffen wurde, nur zu sperren), aber es ist nicht so schnell zum bauen als schaffen std::string
. Es geht mehr darum, die nachfolgenden Vorgänge wie das Überprüfen der Zeichenfolgengleichheit zu beschleunigen, was in meinem Fall nur darauf hinausläuft, zwei Ganzzahlen auf Gleichheit zu überprüfen und die Speichernutzung drastisch zu reduzieren.
Ich denke, eine Option wäre, eine Art Registrierung mit bereits zugewiesenen Werten zu verwalten, aber ist es überhaupt möglich, die Registrierung schneller zu suchen als redundante Speicherzuweisungen?
Es wird schwierig sein, eine Datenstruktur viel schneller als eine einzelne zu durchsuchen malloc
Wenn Sie beispielsweise einen Fall haben, in dem Sie eine Schiffsladung von Zeichenfolgen von einer externen Eingabe wie beispielsweise einer Datei lesen, besteht meine Versuchung darin, wenn möglich einen sequentiellen Allokator zu verwenden. Das hat den Nachteil, dass Sie den Speicher einer einzelnen Zeichenfolge nicht freigeben können. Der gesamte vom Allokator gepoolte Speicher muss sofort oder gar nicht freigegeben werden. Ein sequentieller Allokator kann jedoch nützlich sein, wenn Sie nur eine Schiffsladung winziger Speicherblöcke variabler Größe direkt sequentiell zuweisen müssen, um sie später wieder wegzuwerfen. Ich weiß nicht, ob dies in Ihrem Fall zutrifft oder nicht, aber wenn zutreffend, kann es eine einfache Möglichkeit sein, einen Hotspot zu beheben, der mit häufigen Speicherzuweisungen für Jugendliche zusammenhängt (was möglicherweise mehr mit Cache-Fehlern und Seitenfehlern als mit dem zugrunde liegenden zu tun hat Algorithmus, der beispielsweise von malloc
) verwendet wird.
Zuordnungen mit fester Größe lassen sich ohne die Einschränkungen für sequentielle Zuordnungen, die Sie daran hindern, bestimmte Speicherblöcke für die spätere Wiederverwendung freizugeben, einfacher beschleunigen. Es ist jedoch ziemlich schwierig, die Zuweisung mit variabler Größe schneller als die Standardzuweisung zu machen. Grundsätzlich malloc
ist es extrem schwierig , einen Speicherzuweiser zu erstellen, der schneller als im Allgemeinen ist, wenn Sie keine Einschränkungen anwenden, die seine Anwendbarkeit einschränken. Eine Lösung besteht darin, einen Allokator mit fester Größe für beispielsweise alle Zeichenfolgen zu verwenden, die 8 Byte oder weniger umfassen, wenn Sie eine Schiffsladung davon haben, und längere Zeichenfolgen sind ein seltener Fall (für den Sie nur den Standardzuweiser verwenden können). Das bedeutet, dass 7 Bytes für 1-Byte-Zeichenfolgen verschwendet werden, aber es sollten allokationsbezogene Hotspots eliminiert werden, wenn Ihre Zeichenfolgen beispielsweise in 95% der Fälle sehr kurz sind.
Eine andere Lösung, die mir gerade eingefallen ist, besteht darin, nicht gerollte verknüpfte Listen zu verwenden, die vielleicht verrückt klingen, mich aber anhören.
Die Idee hier ist, jeden nicht gerollten Knoten zu einer festen Größe anstatt zu einer variablen Größe zu machen. Wenn Sie dies tun, können Sie einen extrem schnellen Chunk-Allokator mit fester Größe verwenden, der Speicher bündelt und Chunks mit fester Größe für miteinander verknüpfte Strings mit variabler Größe zuweist. Dadurch wird der Speicherbedarf nicht verringert, sondern aufgrund der Kosten für die Links wird der Wert tendenziell erhöht. Sie können jedoch mit der nicht gerollten Größe spielen, um ein für Ihre Anforderungen geeignetes Gleichgewicht zu finden. Es ist eine verrückte Idee, sollte aber speicherbezogene Hotspots eliminieren, da Sie jetzt bereits in sperrigen zusammenhängenden Blöcken zugewiesenen Speicher effektiv bündeln können und dennoch die Vorteile haben, Zeichenfolgen einzeln freizugeben. Hier ist ein einfacher alter fester Allokator, den ich geschrieben habe (illustrativer Allokator, den ich für jemand anderen gemacht habe, ohne produktionsbedingte Flusen), den Sie frei verwenden können:
#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;
}