Cara mempercepat kod anda menggunakan cache CPU

Cache CPU mengurangkan latensi memori ketika data diakses dari memori sistem utama. Pembangun boleh dan harus memanfaatkan cache CPU untuk meningkatkan prestasi aplikasi.

Bagaimana cache CPU berfungsi

CPU moden biasanya mempunyai tiga tingkat cache, berlabel L1, L2, dan L3, yang mencerminkan urutan CPU memeriksanya. CPU sering mempunyai cache data, cache arahan (untuk kod), dan cache terpadu (untuk apa sahaja). Mengakses cache ini jauh lebih pantas daripada mengakses RAM: Biasanya, cache L1 kira-kira 100 kali lebih pantas daripada RAM untuk akses data, dan cache L2 25 kali lebih cepat daripada RAM untuk akses data.

Apabila perisian anda berjalan dan perlu memasukkan data atau arahan, cache CPU diperiksa terlebih dahulu, kemudian RAM sistem lebih perlahan, dan akhirnya pemacu cakera jauh lebih perlahan. Itulah sebabnya anda ingin mengoptimumkan kod anda untuk mencari apa yang mungkin diperlukan dari cache CPU terlebih dahulu.

Kod anda tidak dapat menentukan di mana arahan data dan data berada - perkakasan komputer melakukannya - jadi anda tidak dapat memaksa elemen tertentu ke dalam cache CPU. Tetapi anda boleh mengoptimumkan kod anda untuk mengambil ukuran cache L1, L2, atau L3 dalam sistem anda menggunakan Instrumentasi Pengurusan Windows (WMI) untuk mengoptimumkan ketika aplikasi anda mengakses cache dan dengan demikian kinerjanya.

CPU tidak pernah mengakses cache byte byte. Sebagai gantinya, mereka membaca memori dalam baris cache, yang merupakan sebahagian memori yang berukuran 32, 64, atau 128 byte.

Penyenaraian kod berikut menggambarkan bagaimana anda dapat mengambil ukuran cache CPU L2 atau L3 di sistem anda:

awam static uint GetCPUCacheSize (string cacheType) {cuba {menggunakan (ManagementObject managementObject = new ManagementObject ("Win32_Processor.DeviceID = 'CPU0'")) {return (uint) (managementObject [cacheType]); }} tangkap {return 0; }} kekosongan statik Utama (string [] args) {uint L2CacheSize = GetCPUCacheSize ("L2CacheSize"); uint L3CacheSize = GetCPUCacheSize ("L3CacheSize"); Console.WriteLine ("L2CacheSize:" + L2CacheSize.ToString ()); Console.WriteLine ("L3CacheSize:" + L3CacheSize.ToString ()); Konsol. Baca (); }

Microsoft mempunyai dokumentasi tambahan pada kelas W32 Win32_Processor.

Pengaturcaraan untuk prestasi: Contoh kod

Apabila anda mempunyai objek di timbunan, tidak ada overhead pengumpulan sampah. Sekiranya anda menggunakan objek berdasarkan timbunan, selalu ada kos yang berkaitan dengan pengumpulan sampah generasi untuk mengumpulkan atau memindahkan objek di timbunan atau memadatkan memori timbunan. Kaedah yang baik untuk mengelakkan pengumpulan sampah secara berlebihan adalah dengan menggunakan struktur dan bukannya kelas.

Cache berfungsi paling baik jika anda menggunakan struktur data berurutan, seperti array. Urutan berurutan membolehkan CPU dapat membaca di depan dan juga membaca di depan secara spekulatif untuk menjangkakan apa yang mungkin akan diminta selanjutnya. Oleh itu, algoritma yang mengakses memori secara berurutan sentiasa pantas.

Sekiranya anda mengakses memori dalam urutan rawak, CPU memerlukan baris cache baru setiap kali anda mengakses memori. Itu mengurangkan prestasi.

Coretan kod berikut menerapkan program mudah yang menggambarkan faedah menggunakan struktur di dalam kelas:

 struct RectangleStruct {luas int awam; ketinggian int awam; } kelas RectangleClass {luas int awam; ketinggian int awam; }

Kod berikut memaparkan prestasi penggunaan susunan struktur terhadap pelbagai kelas. Untuk tujuan ilustrasi, saya telah menggunakan sejuta objek untuk kedua-duanya, tetapi biasanya anda tidak memerlukan banyak objek dalam aplikasi anda.

kekosongan statik Utama (string [] args) {const int size = 1000000; var structs = RectangleStruct baru [saiz]; kelas var = RectangleClass baru [size]; var sw = Jam randik baru (); sw. Mulakan (); untuk (var i = 0; i <size; ++ i) {structs [i] = RectangleStruct baru (); structs [i] .bidang = 0 struktur } var structTime = sw.ElapsedMilliseconds; sw.Reset (); sw. Mulakan (); untuk (var i = 0; i <size; ++ i) {class [i] = RectangleClass baru (); kelas [i]. lebar = 0; kelas [i] .tinggi = 0; } class classTime = sw.ElapsedMilliseconds; sw. Berhenti (); Console.WriteLine ("Masa diambil oleh pelbagai kelas:" + classTime.ToString () + "milisaat."); Console.WriteLine ("Masa yang diambil oleh pelbagai struktur:" + structTime.ToString () + "milisaat."); Konsol. Baca (); }

Programnya mudah: Ia menghasilkan 1 juta objek struktur dan menyimpannya dalam pelbagai. Ia juga menghasilkan 1 juta objek kelas dan menyimpannya dalam array lain. Lebar dan tinggi sifat diberi nilai sifar pada setiap kejadian.

Seperti yang anda lihat, menggunakan struktur mesra cache memberikan keuntungan prestasi yang besar.

Peraturan asas untuk penggunaan cache CPU yang lebih baik

Jadi, bagaimana anda menulis kod yang paling baik menggunakan cache CPU? Malangnya, tidak ada formula ajaib. Tetapi ada beberapa peraturan:

  • Elakkan menggunakan algoritma dan struktur data yang menunjukkan corak akses memori yang tidak teratur; sebaliknya gunakan struktur data linear.
  • Gunakan jenis data yang lebih kecil dan atur data sehingga tidak ada lubang penjajaran.
  • Pertimbangkan corak akses dan manfaatkan struktur data linier.
  • Tingkatkan lokaliti ruang, yang menggunakan setiap baris cache ke tahap maksimum setelah dipetakan ke cache.