Saya sangat bergantung pada string yang diinternir seperti yang disarankan Basile, di mana pencarian string diterjemahkan ke indeks 32-bit untuk disimpan dan dibandingkan. Itu berguna dalam kasus saya karena saya kadang-kadang memiliki ratusan ribu hingga jutaan komponen dengan properti bernama "x", misalnya, yang masih harus berupa nama string yang mudah digunakan karena sering diakses oleh skrip dengan nama.
Saya menggunakan trie untuk pencarian (bereksperimen juga dengan unordered_map
tetapi tri saya disetel yang didukung oleh kolam memori setidaknya mulai berkinerja lebih baik dan juga lebih mudah untuk membuat thread-aman tanpa hanya mengunci setiap kali struktur diakses) tetapi tidak se cepat untuk konstruksi sebagai menciptakan std::string
. Intinya adalah lebih untuk mempercepat operasi selanjutnya seperti memeriksa kesetaraan string yang, dalam kasus saya, hanya bermuara pada memeriksa dua bilangan bulat untuk kesetaraan dan secara drastis mengurangi penggunaan memori.
Saya kira salah satu opsi adalah untuk mempertahankan semacam registri dari nilai yang sudah dialokasikan tetapi apakah mungkin untuk membuat pencarian registri lebih cepat daripada alokasi memori yang berlebihan?
Itu akan sulit untuk melakukan pencarian melalui struktur data jauh lebih cepat daripada satu malloc
, misalnya. Jika Anda memiliki kasus di mana Anda membaca sekumpulan string dari input eksternal seperti file, misalnya, maka godaan saya adalah menggunakan pengalokasi berurutan jika memungkinkan. Itu datang dengan downside bahwa Anda tidak dapat membebaskan memori dari string individu. Semua memori yang dikumpulkan oleh pengalokasi harus dibebaskan sekaligus atau tidak sama sekali. Tetapi pengalokasi sekuensial dapat berguna dalam kasus-kasus di mana Anda hanya perlu mengalokasikan satu kapal penuh potongan memori berukuran variabel dalam mode berurutan lurus, hanya untuk kemudian membuang semuanya nanti. Saya tidak tahu apakah itu berlaku dalam kasus Anda atau tidak, tetapi ketika berlaku, ini bisa menjadi cara mudah untuk memperbaiki hotspot terkait dengan alokasi memori yang sering kali lebih kecil (yang mungkin lebih berkaitan dengan kesalahan cache dan kesalahan halaman daripada yang mendasarinya algoritma yang digunakan oleh, katakanlah, malloc
).
Alokasi berukuran tetap lebih mudah untuk dipercepat tanpa kendala pengalokasian berurutan yang mencegah Anda membebaskan potongan memori tertentu untuk digunakan kembali nanti. Tetapi membuat alokasi ukuran variabel lebih cepat dari pengalokasi default cukup sulit. Pada dasarnya membuat segala jenis pengalokasi memori yang lebih cepat dari malloc
biasanya sangat sulit jika Anda tidak menerapkan kendala yang mempersempit penerapannya. Salah satu solusinya adalah dengan menggunakan pengalokasi berukuran tetap untuk, katakanlah, semua string yang 8 byte atau kurang jika Anda memiliki muatan kapal dan string yang lebih panjang adalah kasus yang jarang terjadi (di mana Anda dapat menggunakan pengalokasi default). Itu berarti 7 byte terbuang untuk string 1-byte, tetapi harus menghilangkan hotspot terkait alokasi, jika, katakanlah, 95% dari waktu, string Anda sangat pendek.
Solusi lain yang baru saja terpikir oleh saya adalah dengan menggunakan daftar tertaut yang tidak terdaftar yang mungkin terdengar gila tapi dengarkan saya.
Idenya di sini adalah untuk membuat setiap node yang tidak dikontrol menjadi ukuran tetap dan bukan ukuran variabel. Saat Anda melakukannya, Anda dapat menggunakan pengalokasi potongan berukuran sangat cepat yang mengumpulkan memori, mengalokasikan potongan berukuran tetap untuk string berukuran variabel yang dihubungkan bersama. Itu tidak akan mengurangi penggunaan memori, itu akan cenderung menambahnya karena biaya tautan, tetapi Anda dapat bermain dengan ukuran yang tidak terbuka untuk menemukan keseimbangan yang cocok untuk kebutuhan Anda. Ini semacam ide aneh tetapi harus menghilangkan hotspot terkait memori karena sekarang Anda dapat secara efektif menyatukan memori yang sudah dialokasikan di blok-blok besar yang berdekatan dan masih memiliki manfaat membebaskan string secara individual. Berikut ini adalah pengalokasi tetap sederhana yang saya tulis (ilustrasi yang saya buat untuk orang lain, tanpa bulu yang terkait dengan produksi) yang dapat Anda gunakan dengan bebas:
#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;
}