Pertimbangkan program komputer yang sangat sederhana berikut ini:
for i = 1 to n:
y[i] = x[p[i]]
Di sini dan adalah elemen array byte, dan adalah array elemen kata. Di sini berukuran besar, misalnya, (sehingga hanya sebagian kecil dari data yang cocok dengan segala jenis memori cache).
Asumsikan bahwa terdiri dari angka acak , terdistribusi secara seragam antara dan .1 n
Dari perspektif perangkat keras modern, ini harus berarti sebagai berikut:
- membaca murah (membaca berurutan)
- membaca sangat mahal (bacaan acak; hampir semua bacaan adalah kesalahan cache; kita harus mengambil setiap byte individual dari memori utama)
- menulis murah (menulis berurutan).
Dan ini memang yang saya amati. Program ini sangat lambat dibandingkan dengan program yang hanya membaca dan menulis berurutan. Bagus.
Sekarang muncul pertanyaan: seberapa baik program ini berparalel pada platform multi-core modern?
Hipotesis saya adalah bahwa program ini tidak sejajar dengan baik. Bagaimanapun, bottleneck adalah memori utama. Satu core sudah menghabiskan sebagian besar waktunya hanya menunggu beberapa data dari memori utama.
Namun, ini bukan yang saya amati ketika saya mulai bereksperimen dengan beberapa algoritma di mana hambatannya adalah operasi semacam ini!
Saya hanya mengganti naif untuk-loop dengan paralel OpenMP untuk-loop (pada dasarnya, itu hanya akan membagi kisaran ke bagian yang lebih kecil dan menjalankan bagian-bagian ini pada core CPU yang berbeda secara paralel).
Pada komputer low-end, speedup memang kecil. Tetapi pada platform yang lebih tinggi saya terkejut bahwa saya mendapatkan speedup dekat-linear yang sangat baik. Beberapa contoh konkret (ketepatan waktu mungkin sedikit tidak tepat, ada banyak variasi acak; ini hanya eksperimen cepat):
2 x 4-core Xeon (total 8 core): faktor 5-8 percepatan dibandingkan dengan versi single-threaded.
2 x 6-core Xeon (total 12 core): faktor 8-14 percepatan dibandingkan dengan versi single-threaded.
Sekarang ini sama sekali tidak terduga. Pertanyaan:
Justru mengapa program semacam ini berparalel dengan sangat baik ? Apa yang terjadi pada perangkat keras? (Dugaan saya saat ini adalah sesuatu di sepanjang baris ini: pembacaan acak dari utas berbeda adalah "pipelined" dan tingkat rata-rata untuk mendapatkan jawaban untuk ini jauh lebih tinggi daripada dalam hal satu utas.)
Apakah perlu menggunakan beberapa utas dan beberapa inti untuk mendapatkan speedup? Jika semacam pipelining memang terjadi di antarmuka antara memori utama dan CPU, tidak bisakah aplikasi berulir tunggal membiarkan memori utama tahu bahwa itu akan segera membutuhkan , x [ p [ i + 1 ] ] , ... dan komputer bisa mulai mengambil garis cache yang relevan dari memori utama? Jika ini mungkin pada prinsipnya, bagaimana cara mencapainya dalam praktik?
Apa hak model teoritis yang bisa kita gunakan untuk menganalisis jenis program (dan membuat yang benar prediksi kinerja)?
Sunting: Sekarang ada beberapa kode sumber dan hasil benchmark tersedia di sini: https://github.com/suomela/parallel-random-read
Beberapa contoh angka rata-rata ( ):
- sekitar 42 ns per iterasi (baca acak) dengan utas tunggal
- sekitar 5 ns per iterasi (baca acak) dengan 12 core.
sumber
Saya memutuskan untuk mencoba __builtin_prefetch () sendiri. Saya memposting di sini sebagai jawaban kalau-kalau orang lain ingin mengujinya di mesin mereka. Hasilnya mendekati apa yang dijelaskan Jukka: Tentang penurunan 20% dalam waktu berjalan saat mengambil 20 elemen di depan dibandingkan dengan mengambil 0 elemen di depan.
Hasil:
Kode:
sumber
Akses DDR3 memang disalurkan melalui pipa. http://www.eng.utah.edu/~cs7810/pres/dram-cs7810-protocolx2.pdf slide 20 dan 24 menunjukkan apa yang terjadi di bus memori selama operasi baca yang disalurkan melalui pipa.
(sebagian salah, lihat di bawah) Beberapa utas tidak diperlukan jika arsitektur CPU mendukung prefetch cache. Modern x86 dan ARM serta banyak arsitektur lainnya memiliki instruksi prefetch eksplisit. Selain itu banyak upaya untuk mendeteksi pola dalam akses memori dan melakukan pengambilan awal secara otomatis. Dukungan perangkat lunak adalah khusus untuk kompiler, misalnya GCC dan Dentang memiliki __builtin_prefech () intrinsik untuk prefetching eksplisit.
Hyperhreading ala Intel tampaknya bekerja sangat baik untuk program yang menghabiskan sebagian besar waktu mereka menunggu kesalahan cache. Dalam pengalaman saya, dalam beban kerja komputasi intensif percepatan berjalan sangat sedikit di atas jumlah core fisik.
EDIT: Saya salah dalam poin 2. Tampaknya sementara prefetching dapat mengoptimalkan akses memori untuk single core, bandwidth memori gabungan dari beberapa core lebih besar dari bandwidth core tunggal. Seberapa besar, tergantung pada CPU.
Prefetcher perangkat keras dan optimasi lainnya bersama-sama membuat pembandingan sangat rumit. Dimungkinkan untuk membuat kasus-kasus di mana prefetching eksplisit memiliki efek yang sangat terlihat atau tidak ada pada kinerja, tolok ukur ini menjadi salah satu yang terakhir.
sumber