Pertanyaan saya adalah, karena saya tidak mengulangi secara linear satu array yang berdekatan pada satu waktu dalam kasus ini, apakah saya langsung mengorbankan keuntungan kinerja dari mengalokasikan komponen dengan cara ini?
Kemungkinannya adalah Anda akan mendapatkan lebih sedikit cache secara keseluruhan dengan array "vertikal" terpisah per tipe komponen daripada interleaving komponen yang melekat pada suatu entitas dalam blok ukuran variabel "horizontal".
Alasannya adalah karena, pertama, representasi "vertikal" akan cenderung menggunakan lebih sedikit memori. Anda tidak perlu khawatir tentang penyelarasan untuk array homogen yang dialokasikan secara berdekatan. Dengan tipe non-homogen yang dialokasikan ke dalam kumpulan memori, Anda harus khawatir tentang perataan karena elemen pertama dalam larik dapat memiliki ukuran dan persyaratan perataan yang sama sekali berbeda dari yang kedua. Akibatnya, Anda harus sering menambahkan bantalan, seperti contoh sederhana:
// Assuming 8-bit chars and 64-bit doubles.
struct Foo
{
// 1 byte
char a;
// 1 byte
char b;
};
struct Bar
{
// 8 bytes
double opacity;
// 8 bytes
double radius;
};
Katakanlah kita ingin interleave Foo
dan Bar
dan menyimpannya tepat di samping satu sama lain dalam memori:
// Assuming 8-bit chars and 64-bit doubles.
struct FooBar
{
// 1 byte
char a;
// 1 byte
char b;
// 6 bytes padding for 64-bit alignment of 'opacity'
// 8 bytes
double opacity;
// 8 bytes
double radius;
};
Sekarang alih-alih mengambil 18 byte untuk menyimpan Foo dan Bar di wilayah memori yang terpisah, dibutuhkan 24 byte untuk menggabungkannya. Tidak masalah jika Anda menukar pesanan:
// Assuming 8-bit chars and 64-bit doubles.
struct BarFoo
{
// 8 bytes
double opacity;
// 8 bytes
double radius;
// 1 byte
char a;
// 1 byte
char b;
// 6 bytes padding for 64-bit alignment of 'opacity'
};
Jika Anda mengambil lebih banyak memori dalam konteks akses berurutan tanpa meningkatkan pola akses secara signifikan, maka Anda biasanya akan mengalami lebih banyak kesalahan cache. Selain itu, langkah untuk berpindah dari satu entitas ke entitas lain meningkat dan ke ukuran variabel, membuat Anda harus mengambil lompatan berukuran variabel dalam memori untuk berpindah dari satu entitas ke entitas lain hanya untuk melihat mana yang memiliki komponen yang Anda inginkan. tertarik pada.
Jadi menggunakan representasi "vertikal" seperti yang Anda lakukan untuk menyimpan tipe komponen sebenarnya lebih mungkin lebih optimal daripada alternatif "horisontal". Yang mengatakan, masalah dengan kesalahan cache dengan representasi vertikal dapat dicontohkan di sini:
Di mana panah hanya mengindikasikan bahwa entitas "memiliki" komponen. Kita dapat melihat bahwa jika kita mencoba mengakses semua gerakan dan merender komponen dari entitas yang memiliki keduanya, kita berakhir melompati semua tempat di memori. Pola akses sporadis semacam itu dapat membuat Anda memuat data ke dalam garis cache untuk mengakses, katakanlah, komponen gerak, lalu mengakses lebih banyak komponen dan meminta agar data sebelumnya digusur, hanya untuk memuat kembali wilayah memori yang sama yang sudah diusir untuk gerakan lain komponen. Sehingga bisa sangat boros memuat wilayah memori yang sama persis lebih dari satu kali ke dalam garis cache hanya untuk mengulang dan mengakses daftar komponen.
Mari kita bersihkan kekacauan itu sedikit sehingga kita bisa melihat lebih jelas:
Perhatikan bahwa jika Anda menghadapi skenario semacam ini, biasanya lama setelah game mulai berjalan, setelah banyak komponen dan entitas telah ditambahkan dan dihapus. Secara umum ketika permainan dimulai, Anda dapat menambahkan semua entitas dan komponen yang relevan bersama-sama, pada titik mana mereka mungkin memiliki pola akses sekuensial yang sangat teratur dengan lokalitas spasial yang baik. Setelah banyak pemindahan dan penyisipan, Anda mungkin akhirnya mendapatkan sesuatu seperti kekacauan di atas.
Cara yang sangat mudah untuk memperbaiki situasi itu adalah dengan hanya mengurutkan komponen Anda berdasarkan ID entitas / indeks yang memilikinya. Pada titik itu Anda mendapatkan sesuatu seperti ini:
Dan itu pola akses yang lebih ramah cache. Itu tidak sempurna karena kita dapat melihat bahwa kita harus melewatkan beberapa komponen rendering dan gerakan di sana-sini karena sistem kita hanya tertarik pada entitas yang memiliki keduanya , dan beberapa entitas hanya memiliki komponen gerak dan beberapa hanya memiliki komponen rendering , tetapi Anda setidaknya akhirnya dapat memproses beberapa komponen yang berdekatan (lebih banyak dalam praktiknya, biasanya, karena sering kali Anda akan melampirkan komponen menarik yang relevan, seperti mungkin lebih banyak entitas dalam sistem Anda yang memiliki komponen gerak akan memiliki komponen rendering daripada tidak).
Yang paling penting, setelah Anda mengurutkan ini, Anda tidak akan memuat data wilayah memori ke dalam garis cache hanya untuk kemudian memuatnya kembali dalam satu lingkaran.
Dan ini tidak memerlukan desain yang sangat kompleks, hanya semacam radix linear-waktu berlalu setiap sekarang dan kemudian, mungkin setelah Anda memasukkan dan menghapus banyak komponen untuk jenis komponen tertentu, pada titik mana Anda dapat menandainya sebagai perlu disortir. Jenis radix yang diimplementasikan secara wajar (Anda bahkan dapat memparalelkannya, yang saya lakukan) dapat mengurutkan sejuta elemen dalam sekitar 6ms pada quad-core i7 saya, seperti yang dicontohkan di sini:
Sorting 1000000 elements 32 times...
mt_sort_int: {0.203000 secs}
-- small result: [ 22 48 59 77 79 80 84 84 93 98 ]
mt_sort: {1.248000 secs}
-- small result: [ 22 48 59 77 79 80 84 84 93 98 ]
mt_radix_sort: {0.202000 secs}
-- small result: [ 22 48 59 77 79 80 84 84 93 98 ]
std::sort: {1.810000 secs}
-- small result: [ 22 48 59 77 79 80 84 84 93 98 ]
qsort: {2.777000 secs}
-- small result: [ 22 48 59 77 79 80 84 84 93 98 ]
Di atas adalah untuk mengurutkan sejuta elemen 32 kali (termasuk waktu untuk memcpy
hasil sebelum dan sesudah pengurutan). Dan saya berasumsi sebagian besar waktu Anda tidak akan benar-benar memiliki komponen juta + untuk disortir, jadi Anda harus dengan mudah dapat menyelinap ini sekarang dan di sana tanpa menyebabkan gagap frame rate yang terlihat.