Salah satu kasus paling berguna yang saya temukan untuk daftar tertaut yang bekerja di bidang kinerja-kritis seperti pemrosesan mesh dan gambar, mesin fisika, dan raytracing adalah saat menggunakan daftar tertaut benar-benar meningkatkan lokalitas referensi dan mengurangi alokasi heap dan terkadang bahkan mengurangi penggunaan memori dibandingkan dengan alternatif langsung.
Sekarang itu bisa tampak seperti oxymoron lengkap bahwa daftar tertaut dapat melakukan semua itu karena mereka terkenal sering melakukan yang sebaliknya, tetapi mereka memiliki properti unik di mana setiap node daftar memiliki ukuran tetap dan persyaratan penyelarasan yang dapat kita manfaatkan untuk mengizinkan mereka untuk disimpan secara berdekatan dan dihapus dalam waktu konstan dengan cara yang tidak dapat dilakukan oleh hal-hal berukuran variabel.
Akibatnya, mari kita ambil kasus di mana kita ingin melakukan persamaan analogis dengan menyimpan urutan panjang variabel yang berisi satu juta sub-urutan panjang variabel bersarang. Contoh konkretnya adalah jaring terindeks yang menyimpan sejuta poligon (beberapa segitiga, beberapa paha depan, beberapa segi lima, beberapa segi enam, dll) dan kadang-kadang poligon dihapus dari mana saja di jaring dan terkadang poligon dibangun kembali untuk memasukkan simpul ke poligon yang ada atau hapus satu. Dalam hal ini, jika kita menyimpan satu juta kecil std::vectors
, maka kita akan menghadapi alokasi heap untuk setiap vektor serta penggunaan memori yang berpotensi meledak. Satu juta orang kecil SmallVectors
mungkin tidak mengalami masalah ini sebanyak dalam kasus umum, tetapi buffer yang telah dialokasikan sebelumnya yang tidak dialokasikan secara terpisah mungkin masih menyebabkan penggunaan memori yang eksplosif.
Masalahnya di sini adalah satu juta std::vector
contoh akan mencoba menyimpan sejuta benda dengan panjang variabel. Hal-hal dengan panjang variabel cenderung menginginkan alokasi heap karena tidak dapat disimpan secara efektif dan dihapus dalam waktu konstan (setidaknya dengan cara yang langsung tanpa pengalokasi yang sangat kompleks) jika mereka tidak menyimpan kontennya di tempat lain di heap.
Sebaliknya, jika kita melakukan ini:
struct FaceVertex
{
// Points to next vertex in polygon or -1
// if we're at the end of the polygon.
int next;
...
};
struct Polygon
{
// Points to first vertex in polygon.
int first_vertex;
...
};
struct Mesh
{
// Stores all the face vertices for all polygons.
std::vector<FaceVertex> fvs;
// Stores all the polygons.
std::vector<Polygon> polys;
};
... maka kami telah secara dramatis mengurangi jumlah alokasi heap dan cache yang hilang. Alih-alih memerlukan alokasi heap dan kemungkinan cache wajib hilang untuk setiap poligon yang kita akses, sekarang kita hanya memerlukan alokasi heap ketika salah satu dari dua vektor yang disimpan di seluruh mesh melebihi kapasitasnya (biaya diamortisasi). Dan sementara langkah untuk berpindah dari satu simpul ke simpul berikutnya mungkin masih menyebabkan bagian cache-nya meleset, itu masih sering kurang dari jika setiap poligon menyimpan larik dinamis yang terpisah karena simpul disimpan secara berdekatan dan ada kemungkinan simpul tetangga mungkin diakses sebelum penggusuran (terutama mengingat banyak poligon akan menambahkan simpulnya sekaligus yang membuat bagian terbesar dari simpul poligon bersebelahan sempurna).
Berikut contoh lainnya:
... di mana sel grid digunakan untuk mempercepat tumbukan partikel-partikel untuk, katakanlah, 16 juta partikel yang bergerak setiap bingkai. Dalam contoh kisi partikel itu, dengan menggunakan daftar tertaut kita dapat memindahkan partikel dari satu sel kisi ke kisi lainnya hanya dengan mengubah 3 indeks. Menghapus dari vektor dan mendorong kembali ke vektor lain bisa jadi jauh lebih mahal dan memperkenalkan lebih banyak alokasi heap. Daftar tertaut juga mengurangi memori sel hingga 32-bit. Sebuah vektor, bergantung pada implementasinya, dapat mengalokasikan larik dinamisnya ke titik di mana ia membutuhkan 32 byte untuk vektor kosong. Jika kita memiliki sekitar satu juta sel grid, itu sangat berbeda.
... dan di sinilah saya menemukan daftar tertaut yang paling berguna saat ini, dan saya secara khusus menemukan variasi "daftar tertaut yang diindeks" berguna karena indeks 32-bit membagi separuh persyaratan memori dari tautan pada mesin 64-bit dan mereka menyiratkan bahwa node disimpan secara berdekatan dalam sebuah array.
Seringkali saya juga menggabungkannya dengan daftar gratis yang diindeks untuk memungkinkan penghapusan dan penyisipan waktu konstan di mana saja:
Dalam hal ini, next
indeks menunjuk ke indeks bebas berikutnya jika simpul telah dihapus atau indeks yang digunakan selanjutnya jika simpul belum dihapus.
Dan ini adalah kasus penggunaan nomor satu yang saya temukan untuk daftar tertaut hari ini. Ketika kita ingin menyimpan, katakanlah, satu juta sub-urutan dengan panjang variabel yang rata-rata, katakanlah, masing-masing 4 elemen (tetapi terkadang dengan elemen yang dihapus dan ditambahkan ke salah satu sub-urutan ini), daftar tertaut memungkinkan kita untuk menyimpan 4 juta node daftar tertaut berdekatan alih-alih 1 juta kontainer yang masing-masing dialokasikan heap secara individual: satu vektor raksasa, yaitu, bukan satu juta vektor kecil.