Semacam menggemakan saran Kylotan tetapi saya akan merekomendasikan untuk menyelesaikan ini di tingkat struktur data bila memungkinkan, bukan pada tingkat pengalokasi yang lebih rendah jika Anda dapat membantu.
Berikut adalah contoh sederhana tentang bagaimana Anda dapat menghindari mengalokasikan dan membebaskan Foos
berulang kali menggunakan array dengan lubang dengan elemen yang terhubung bersama (menyelesaikan ini di tingkat "wadah" alih-alih tingkat "pengalokasi"):
struct FooNode
{
explicit FooNode(const Foo& ielement): element(ielement), next(-1) {}
// Stores a 'Foo'.
Foo element;
// Points to the next foo available; either the
// next used foo or the next deleted foo. Can
// use SoA and hoist this out if Foo doesn't
// have 32-bit alignment.
int next;
};
struct Foos
{
// Stores all the Foo nodes.
vector<FooNode> nodes;
// Points to the first used node.
int first_node;
// Points to the first free node.
int free_node;
Foos(): first_node(-1), free_node(-1)
{
}
const FooNode& operator[](int n) const
{
return data[n];
}
void insert(const Foo& element)
{
int index = free_node;
if (index != -1)
{
// If there's a free node available,
// pop it from the free list, overwrite it,
// and push it to the used list.
free_node = data[index].next;
data[index].next = first_node;
data[index].element = element;
first_node = index;
}
else
{
// If there's no free node available, add a
// new node and push it to the used list.
FooNode new_node(element);
new_node.next = first_node;
first_node = data.size() - 1;
data.push_back(new_node);
}
}
void erase(int n)
{
// If the node being removed is the first used
// node, pop it from the used list.
if (first_node == n)
first_node = data[n].next;
// Push the node to the free list.
data[n].next = free_node;
free_node = n;
}
};
Sesuatu untuk efek ini: daftar indeks yang terhubung sendiri dengan daftar gratis. Tautan indeks memungkinkan Anda untuk melewati elemen yang dihapus, menghapus elemen dalam waktu konstan, dan juga mendapatkan kembali / menggunakan kembali / menimpa elemen gratis dengan penyisipan waktu konstan. Untuk beralih melalui struktur, Anda melakukan sesuatu seperti ini:
for (int index = foos.first_node; index != -1; index = foos[index].next)
// do something with foos[index]
Dan Anda dapat menggeneralisasi jenis "array array lubang" di atas dengan menggunakan template, penempatan permintaan dokumen baru dan manual untuk menghindari persyaratan penugasan salinan, membuatnya memohon destruktor ketika elemen dihapus, berikan iterator maju, dll. Saya memilih untuk menyimpan contoh sangat C-suka untuk lebih menggambarkan konsep dan juga karena saya sangat malas.
Yang mengatakan, struktur ini cenderung menurun di lokasi spasial setelah Anda menghapus dan memasukkan banyak hal ke / dari tengah. Pada titik itu, next
tautan bisa membuat Anda berjalan bolak-balik di sepanjang vektor, memuat ulang data yang sebelumnya diusir dari garis cache dalam lintasan sekuensial yang sama (ini tidak bisa dihindari dengan struktur data atau pengalokasi yang memungkinkan penghapusan waktu-konstan tanpa mengocok elemen saat mengklaim kembali spasi dari tengah dengan penyisipan waktu-konstan dan tanpa menggunakan sesuatu seperti bitset paralel atau removed
bendera). Untuk memulihkan keramahan cache, Anda dapat menerapkan metode copy ctor dan swap seperti ini:
Foos(const Foos& other)
{
for (int index = other.first_node; index != -1; index = other[index].next)
insert(foos[index].element);
}
void Foos::swap(Foos& other)
{
nodes.swap(other.nodes):
std::swap(first_node, other.first_node);
std::swap(free_node, other.free_node);
}
// ... then just copy and swap:
Foos(foos).swap(foos);
Sekarang versi baru ini ramah cache lagi untuk dilintasi. Metode lain adalah menyimpan daftar indeks yang terpisah ke dalam struktur dan mengurutkannya secara berkala. Cara lain adalah menggunakan bitet untuk menunjukkan indeks apa yang digunakan. Itu akan selalu membuat Anda melintasi bitset secara berurutan (untuk melakukan ini secara efisien, periksa 64-bit pada suatu waktu misalnya menggunakan FFS / FFZ). Bitet adalah yang paling efisien dan tidak mengganggu, hanya membutuhkan bit paralel per elemen untuk menunjukkan mana yang digunakan dan mana yang dihapus alih-alih membutuhkan 32-bitnext
indeks , tetapi yang paling memakan waktu untuk menulis dengan baik (itu tidak akan cepat untuk traversal jika Anda mengecek satu bit pada satu waktu - Anda perlu FFS / FFZ untuk menemukan set atau unset bit segera di antara 32+ bit sekaligus untuk secara cepat menentukan rentang indeks yang ditempati).
Solusi tertaut ini umumnya paling mudah diterapkan dan tidak mengganggu (tidak perlu dimodifikasi Foo
untuk menyimpan beberapa removed
flag) yang bermanfaat jika Anda ingin menggeneralisasi wadah ini untuk bekerja dengan tipe data apa pun jika Anda tidak keberatan 32-bit overhead per elemen.
Haruskah saya membuat kumpulan memori untuk alokasi dinamis, atau apakah tidak perlu repot dengan ini? Bagaimana jika platform target adalah perangkat seluler?
perlu adalah kata yang kuat dan saya bias bekerja di area yang sangat kritis terhadap kinerja seperti raytracing, pemrosesan gambar, simulasi partikel, dan pemrosesan mesh, tetapi relatif sangat mahal untuk mengalokasikan dan membebaskan objek kecil yang digunakan untuk pemrosesan yang sangat ringan seperti peluru dan partikel-partikel secara terpisah melawan pengalokasi memori berukuran besar yang bertujuan umum. Mengingat bahwa Anda harus dapat menggeneralisasi struktur data di atas dalam satu atau dua hari untuk menyimpan apa pun yang Anda inginkan, saya pikir itu akan menjadi pertukaran yang bermanfaat untuk menghilangkan biaya tumpukan / alokasi yang begitu saja dari pembayaran untuk setiap hal kecil. Selain mengurangi biaya alokasi / deallokasi, Anda mendapatkan lokalitas referensi yang lebih baik melintasi hasil (cache lebih sedikit dan kesalahan halaman, yaitu).
Adapun apa yang Josh sebutkan tentang GC, saya belum mempelajari implementasi GC C sedekat Jawa, tetapi pengalokasi GC sering memiliki alokasi awalitu sangat cepat karena itu menggunakan pengalokasi berurutan yang tidak dapat membebaskan memori dari tengah (hampir seperti tumpukan, Anda tidak dapat menghapus hal-hal dari tengah). Kemudian membayar biaya mahal untuk benar-benar memungkinkan menghapus objek individu di utas terpisah dengan menyalin memori dan membersihkan memori yang sebelumnya dialokasikan sebagai keseluruhan (seperti menghancurkan seluruh tumpukan sekaligus sekaligus menyalin data ke sesuatu yang lebih seperti struktur yang terhubung), tetapi karena dilakukan di utas terpisah, itu tidak selalu menghambat utas aplikasi Anda. Namun, itu membawa biaya tersembunyi yang sangat signifikan dari tingkat tipuan tambahan dan kerugian umum LOR setelah siklus GC awal. Ini adalah strategi lain untuk mempercepat alokasi - membuatnya lebih murah di utas panggilan dan kemudian melakukan pekerjaan mahal di yang lain. Untuk itu Anda perlu dua tingkat tipuan untuk referensi objek Anda, bukan satu karena mereka akhirnya akan terseret dalam memori antara waktu Anda awalnya mengalokasikan dan setelah siklus pertama.
Strategi lain dalam nada yang serupa yang sedikit lebih mudah diterapkan di C ++ hanya tidak perlu repot untuk membebaskan objek Anda di utas utama Anda. Terus menambahkan dan menambahkan dan menambahkan ke ujung struktur data yang tidak memungkinkan menghapus hal-hal dari tengah. Namun, tandai hal-hal yang perlu dihapus. Kemudian utas terpisah dapat menangani pekerjaan mahal untuk membuat struktur data baru tanpa elemen yang dihapus dan kemudian secara atomis menukar yang baru dengan yang lama, mis. Sebagian besar biaya elemen pengalokasian dan pembebasan dapat diteruskan ke suatu pisahkan utas jika Anda dapat membuat asumsi bahwa meminta untuk menghapus suatu elemen tidak harus segera dipenuhi. Itu tidak hanya membuat membebaskan lebih murah sejauh utas Anda terkait tetapi membuat alokasi lebih murah, karena Anda dapat menggunakan struktur data yang jauh lebih sederhana dan bodoh yang tidak pernah harus menangani kasus penghapusan dari tengah. Ini seperti sebuah wadah yang hanya membutuhkan apush_back
fungsi untuk penyisipan, clear
fungsi untuk menghapus semua elemen, dan swap
untuk menukar konten dengan wadah baru yang ringkas tidak termasuk elemen yang dihapus; itu saja sejauh bermutasi.