Basileが示唆するように、私はインターンされた文字列に大きく依存しています。文字列ルックアップは、保存および比較する32ビットのインデックスに変換されます。これは私の場合に役立ちます。たとえば、「x」というプロパティを持つコンポーネントが数十万から数百万ある場合があるためです。たとえば、スクリプト作成者が名前でアクセスすることが多いため、ユーザーフレンドリーな文字列名である必要があります。
私はルックアップにトライを使用します(unordered_map
ただし、メモリプールに裏打ちされた調整済みトライは少なくともパフォーマンスが向上し始め、構造にアクセスするたびにロックするだけでなくスレッドセーフにするのも簡単になりました)が、作成時の建設に高速ですstd::string
。ポイントは、文字列が等しいかどうかのチェックなど、後続の操作を高速化することです。私の場合、2つの整数が等しいかどうかをチェックし、メモリ使用量を大幅に削減します。
すでに割り当てられた値のある種のレジストリを維持することは1つのオプションだと思いますが、レジストリの検索を冗長なメモリ割り当てよりも高速にすることは可能ですか?
データ構造全体を1つよりもはるかに高速に検索するのは難しいでしょう malloc
たとえば、ファイルなどの外部入力から文字列のボートロードを読み取る場合は、可能であればシーケンシャルアロケーターを使用するのが私の誘惑です。これには、個々の文字列のメモリを解放できないという欠点があります。アロケータによってプールされたすべてのメモリは、一度に解放されるか、まったく解放されない必要があります。しかし、シーケンシャルアロケーターは、可変サイズの小さなメモリチャンクのボートロードをストレートシーケンシャル方式で割り当てる必要がある場合に便利です。それがあなたのケースに当てはまるかどうかはわかりませんが、当てはまる場合、頻繁な小さなメモリ割り当てに関連するホットスポットを修正する簡単な方法になる可能性があります、などによって使用されるアルゴリズムmalloc
)。
固定サイズの割り当ては、特定のメモリチャンクを解放して後で再利用できないようにするシーケンシャルアロケーターの制約がなければ、速度を上げるのが簡単です。しかし、可変サイズの割り当てをデフォルトのアロケーターよりも速くするのはかなり難しいです。基本的にmalloc
、適用範囲を狭める制約を適用しない場合、一般的に非常に困難な速度よりも速い種類のメモリアロケータを作成します。1つの解決策は、たとえば、ボートロードがある場合に8バイト以下のすべての文字列に固定サイズのアロケーターを使用することです。長い文字列はまれなケースです(デフォルトのアロケーターをそのまま使用できます)。つまり、1バイトの文字列では7バイトが無駄になりますが、割り当てに関連するホットスポットは排除されます。たとえば、95%の確率で文字列が非常に短い場合は、
ちょうど私に起こった別の解決策は、狂ったように聞こえるかもしれないが私から聞こえるかもしれない展開されたリンクされたリストを使うことです。
ここでの考え方は、展開された各ノードを可変サイズではなく固定サイズにすることです。これを行うと、メモリをプールする非常に高速な固定サイズのチャンクアロケータを使用して、リンクされた可変サイズの文字列に固定サイズのチャンクを割り当てることができます。メモリ使用量は減りませんが、リンクのコストのために追加される傾向がありますが、展開されたサイズで遊んで、ニーズに適したバランスを見つけることができます。ちょっと変わったアイデアですが、かさばる連続したブロックにすでに割り当てられているメモリを効果的にプールでき、文字列を個別に解放できるという利点があるため、メモリ関連のホットスポットを排除する必要があります。これは私が書いた簡単な古い固定アロケータ(私が他の誰かのために作成したもので、プロダクション関連の綿毛がないもの)で、自由に使用できます。
#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;
}