Seberapa penting penyelarasan memori? Apakah masih penting?

15

Dari beberapa waktu sekarang, saya telah mencari dan membaca banyak tentang penyelarasan memori, cara kerjanya, dan cara menggunakannya. Artikel paling relevan yang saya temukan untuk saat ini adalah artikel ini .

Tetapi bahkan dengan itu saya masih memiliki beberapa pertanyaan tentang itu:

  1. Keluar dari sistem tertanam, kita sering memiliki sejumlah besar memori di komputer kita yang membuat manajemen memori jauh lebih sedikit kritik, saya sepenuhnya ke dalam optimasi, tetapi sekarang, apakah ini benar-benar sesuatu yang dapat membuat perbedaan jika kita membandingkan program yang sama dengan atau tanpa ingatannya ulang dan disejajarkan?
  2. Apakah penyelarasan memori memiliki kelebihan lain? Saya membaca di suatu tempat bahwa CPU bekerja lebih baik / lebih cepat dengan memori yang disejajarkan karena itu membutuhkan lebih sedikit instruksi untuk diproses (jika salah satu dari Anda memiliki tautan untuk artikel / benchmark tentang hal itu?), Dalam hal ini, apakah perbedaannya benar-benar signifikan? Apakah ada keuntungan lebih dari keduanya?
  3. Dalam tautan artikel, pada bab 5, penulis mengatakan:

    Hati-hati: di C ++, kelas-kelas yang terlihat seperti struct dapat melanggar aturan ini! (Apakah mereka melakukannya atau tidak tergantung pada bagaimana kelas dasar dan fungsi anggota virtual diimplementasikan, dan bervariasi menurut kompiler.)

  4. Artikel ini banyak berbicara tentang struktur, tetapi apakah deklarasi variabel lokal juga dipengaruhi oleh kebutuhan ini?

    Pernahkah Anda tahu bagaimana penyelarasan memori bekerja tepat di C ++ karena tampaknya ada beberapa perbedaan?

Pertanyaan sebelumnya berisi kata "alignment", tetapi tidak memberikan jawaban untuk pertanyaan di atas.

Kane
sumber
Kompiler C ++ lebih cenderung melakukan ini (masukkan padding di tempat yang dibutuhkan atau menguntungkan) untuk Anda. Dari tautan yang Anda sebutkan, lihat di bagian 12 "Alat" untuk hal-hal yang dapat Anda gunakan.
rwong

Jawaban:

11

Ya, baik keselarasan dan pengaturan data Anda dapat membuat perbedaan besar dalam kinerja, bukan hanya beberapa persen tetapi beberapa hingga ratusan persen.

Ambil loop ini, dua instruksi penting jika Anda menjalankan loop cukup.

.globl ASMDELAY
ASMDELAY:
    subs r0,r0,#1
    bne ASMDELAY
    bx lr

Dengan dan tanpa cache, dan dengan penyelarasan dengan dan tanpa cache melemparkan dalam prediksi cabang dan Anda dapat memvariasikan kedua instruksi kinerja dengan jumlah yang signifikan (kutu waktu):

min      max      difference
00016DDE 003E025D 003C947F

Tes kinerja yang sangat mudah Anda lakukan sendiri. menambah atau menghapus nops di sekitar kode yang sedang diuji dan melakukan pekerjaan waktu yang akurat, pindahkan instruksi yang sedang diuji di sepanjang rentang alamat yang cukup luas untuk menyentuh tepi garis cache, dll.

Hal yang sama dengan akses data. Beberapa arsitektur mengeluh tentang akses yang tidak selaras (melakukan pembacaan 32 bit di alamat 0x1001 misalnya), dengan memberi Anda kesalahan data. Beberapa di antaranya Anda dapat menonaktifkan kesalahan dan membuat hit kinerja. Orang lain yang memungkinkan akses yang tidak selaras Anda baru saja mendapatkan kinerja yang baik.

Kadang-kadang "instruksi" tetapi sebagian besar waktu adalah siklus jam / bus.

Lihatlah implementasi memcpy di gcc untuk berbagai target. Katakanlah Anda menyalin struktur yang 0x43 byte, Anda mungkin menemukan implementasi yang menyalin satu byte meninggalkan 0x42 kemudian menyalin 0x40 byte dalam potongan besar yang efisien maka 0x2 terakhir dapat dilakukan sebagai dua byte individu atau sebagai transfer 16 bit. Alignment dan target ikut berperan jika sumber dan alamat tujuan berada pada alignment yang sama katakan 0x1003 dan 0x2003, maka Anda bisa melakukan satu byte, kemudian 0x40 dalam potongan besar kemudian 0x2, tetapi jika satu adalah 0x1002 dan 0x1003 lainnya, maka sangat jelek dan sangat lambat.

Sebagian besar waktu adalah siklus bus. Atau lebih buruk jumlah transfer. Ambil prosesor dengan bus data selebar 64 bit, seperti ARM, dan lakukan transfer empat kata (baca atau tulis, LDM atau STM) di alamat 0x1004, itu adalah alamat yang selaras kata, dan legal, tetapi jika bus tersebut 64 lebar bit kemungkinan instruksi tunggal akan berubah menjadi tiga transfer dalam hal ini 32 bit pada 0x1004, 64 bit pada 0x1008 dan 32 bit pada 0x100A. Tetapi jika Anda memiliki instruksi yang sama tetapi pada alamat 0x1008 itu bisa melakukan transfer empat kata tunggal di alamat 0x1008. Setiap transfer memiliki waktu setup yang terkait. Jadi perbedaan alamat 0x1004 hingga 0x1008 dengan sendirinya bisa beberapa kali lebih cepat, bahkan / esp saat menggunakan cache dan semua adalah hit cache.

Omong-omong, bahkan jika Anda membaca dua kata di alamat 0x1000 vs 0x0FFC, 0x0FFC dengan misses cache akan menyebabkan dua baris cache membaca di mana 0x1000 adalah satu baris cache, Anda mendapat penalti dari garis cache dibaca tetap untuk acak akses (membaca lebih banyak data daripada menggunakan) tetapi kemudian dua kali lipat. Bagaimana struktur Anda disejajarkan atau data Anda secara umum dan frekuensi Anda mengakses data itu, dll, dapat menyebabkan penumpukan cache.

Anda dapat menghapus data Anda sehingga saat Anda memproses data yang dapat Anda buat penggusuran, Anda bisa benar-benar tidak beruntung dan hanya menggunakan sebagian kecil dari cache Anda dan ketika Anda melompati gumpalan data berikutnya bertabrakan dengan gumpalan sebelumnya . Dengan mencampur data Anda atau mengatur ulang fungsi dalam kode sumber, dll Anda dapat membuat atau menghapus tabrakan, karena tidak semua cache dibuat sama dengan kompiler tidak akan membantu Anda di sini, itu ada pada Anda. Bahkan mendeteksi hit atau peningkatan kinerja ada pada Anda.

Semua hal yang telah kami tambahkan untuk meningkatkan kinerja, bus data yang lebih luas, jalur pipa, cache, prediksi cabang, beberapa unit / jalur eksekusi, dll. Paling sering akan membantu, tetapi semuanya memiliki titik lemah, yang dapat dieksploitasi baik secara sengaja atau tidak sengaja. Ada sangat sedikit kompiler atau pustaka dapat lakukan tentang hal itu, jika Anda tertarik pada kinerja yang Anda butuhkan untuk menyetel dan salah satu faktor penyetelan terbesar adalah keselarasan kode dan data, tidak hanya selaras pada 32, 64, 128, 256 batas bit, tetapi juga di mana hal-hal relatif satu sama lain, Anda ingin loop yang banyak digunakan atau data yang digunakan kembali untuk tidak mendarat dengan cara cache yang sama, mereka masing-masing menginginkannya sendiri. Compiler dapat membantu, misalnya memesan instruksi untuk arsitektur skalar super, mengatur ulang instruksi yang relatif satu sama lain, tidak masalah,

Pengawasan terbesar adalah asumsi bahwa prosesor adalah hambatan. Belum benar selama satu dekade atau lebih, memberi makan prosesor adalah masalah dan di situlah masalah seperti hit kinerja penyelarasan, meronta-ronta cache, dll ikut bermain. Dengan sedikit kerja bahkan pada tingkat kode sumber, mengatur ulang data dalam suatu struktur, memesan deklarasi variabel / struct, memesan fungsi dalam kode sumber, dan sedikit kode tambahan untuk menyelaraskan data, dapat meningkatkan kinerja beberapa kali lipat atau lebih.

old_timer
sumber
+1 jika hanya untuk paragraf terakhir Anda. Bandwidth memori adalah masalah paling kritis bagi siapa pun yang mencoba menulis kode cepat hari ini, bukan jumlah instruksi. Dan ini berarti bahwa mengoptimalkan hal-hal untuk mengurangi kesalahan cache, yang dapat dilakukan dengan memodifikasi penyelarasan dalam banyak keadaan, sangat penting.
Jules
Jika kode dan data Anda menjadi di-cache dan Anda melakukan cukup loop / siklus pada data itu maka instruksi dihitung dan di mana instruksi berada di dalam garis pengambilan, di mana cabang-cabang mendarat di dalam pipa relatif terhadap apa yang mereka andalkan, memang penting. Namun dalam sistem berbasis dram dan / atau flash, Anda harus lebih dulu khawatir tentang cara memberi makan prosesor.
old_timer
15

Ya, penyelarasan memori masih penting.

Beberapa prosesor sebenarnya tidak dapat melakukan pembacaan pada alamat yang tidak selaras. Jika Anda menggunakan perangkat keras seperti itu, dan Anda menyimpan bilangan bulat tidak selaras, Anda mungkin harus membacanya dengan dua instruksi diikuti oleh beberapa instruksi lagi untuk mendapatkan berbagai byte ke tempat yang tepat sehingga Anda benar-benar dapat menggunakannya . Jadi data yang selaras sangat penting bagi kinerja.

Kabar baiknya adalah Anda sebagian besar sebenarnya tidak perlu peduli. Hampir semua kompiler untuk hampir semua bahasa akan menghasilkan kode mesin yang menghormati persyaratan perataan sistem target. Anda hanya perlu mulai memikirkannya jika Anda mengambil kendali langsung dari representasi dalam-memori data Anda, yang tidak perlu berada di dekat sesering dulu. Ini adalah hal yang menarik untuk diketahui, dan sangat penting untuk mengetahui apakah Anda ingin memahami penggunaan memori dari berbagai struktur yang Anda buat, dan bagaimana mungkin mengatur ulang hal-hal menjadi lebih efisien (menghindari bantalan). Tetapi kecuali jika Anda membutuhkan kontrol semacam itu (dan untuk sebagian besar sistem Anda tidak), Anda dapat dengan senang hati menjalani seluruh karir tanpa mengetahui atau memedulikannya.

Matthew Walton
sumber
1
Secara khusus, ARM tidak mendukung akses yang tidak selaras. Dan itu adalah CPU yang digunakan hampir semua ponsel.
Jan Hudec
Perhatikan juga bahwa Linux mengemulasi akses yang tidak selaras dengan biaya runtime, tetapi Windows (CE dan Telepon) tidak dan upaya akses yang tidak selaras hanya akan merusak aplikasi.
Jan Hudec
2
Walaupun ini sebagian besar benar, perhatikan bahwa beberapa platform (termasuk x86) memiliki persyaratan penyelarasan yang berbeda tergantung pada instruksi yang akan digunakan , yang tidak mudah bagi kompilator untuk bekerja sendiri, sehingga Anda terkadang perlu membuat pad untuk memastikan operasi tertentu (mis. instruksi SSE, banyak di antaranya memerlukan perataan 16 byte) dapat digunakan untuk beberapa operasi. Juga, menambahkan padding tambahan sehingga dua item yang sering digunakan bersama terjadi pada baris cache yang sama (juga 16 byte) dapat memiliki efek besar pada kinerja dalam beberapa kasus, dan juga tidak otomatis.
Jules
3

Ya, itu masih penting, dan dalam beberapa algoritma kritis kinerja, Anda tidak dapat mengandalkan kompiler.

Saya akan mendaftar hanya beberapa contoh:

  1. Dari jawaban ini :

Biasanya, mikrokode akan mengambil kuantitas 4-byte yang tepat dari memori, tetapi jika tidak selaras, ia harus mengambil dua lokasi 4-byte dari memori dan merekonstruksi kuantitas 4-byte yang diinginkan dari byte yang sesuai dari dua lokasi.

  1. Serangkaian instruksi SSE memerlukan penyelarasan khusus. Jika tidak terpenuhi, Anda harus menggunakan fungsi khusus untuk memuat dan menyimpan data ke dalam memori yang tidak selaras. Itu berarti dua instruksi tambahan.

Jika Anda tidak bekerja pada algoritma kritis kinerja, lupakan saja penyelarasan memori. Itu tidak benar-benar diperlukan untuk pemrograman normal.

BЈовић
sumber
1

Kita cenderung menghindari situasi yang penting. Jika itu penting, itu penting. Data yang tidak selaras dulu terjadi misalnya ketika memproses data biner, yang tampaknya dihindari saat ini (orang banyak menggunakan XML atau JSON).

JIKA Anda entah bagaimana berhasil membuat array bilangan bulat yang tidak selaras, maka pada prosesor intel yang khas, kode Anda yang memproses larik itu akan berjalan sedikit lebih lambat daripada untuk data yang selaras. Pada prosesor ARM ini berjalan sedikit lebih lambat jika Anda memberi tahu kompiler bahwa data tidak selaras. Itu bisa menjalankan banyak, sangat lambat lebih lambat atau memberikan hasil yang salah, tergantung pada model prosesor dan sistem operasi, jika Anda menggunakan data yang tidak selaras tanpa memberitahu kompiler.

Menjelaskan referensi ke C ++: Di C, semua bidang dalam struct harus disimpan dalam urutan memori naik. Jadi jika Anda memiliki bidang char / double / char dan ingin semuanya disejajarkan, Anda akan memiliki satu byte char, tujuh byte tidak terpakai, delapan byte ganda, satu byte char, tujuh byte tidak terpakai. Dalam C ++ struct itu sama untuk kompatibilitas. Tetapi untuk struct, kompiler dapat menyusun ulang bidang, sehingga Anda mungkin memiliki satu byte char, byte byte yang lain, enam byte yang tidak digunakan, 8 byte ganda. Menggunakan 16 bukannya 24 byte. Dalam C struct, pengembang biasanya akan menghindari situasi itu dan menempatkan bidang dalam urutan yang berbeda sejak awal.

gnasher729
sumber
1
Data yang tidak selaras terjadi dalam memori. Program yang tidak memiliki struktur data yang dikemas dengan baik dapat mengalami hukuman kinerja yang sangat besar bahkan untuk pemesanan nilai yang tampaknya tidak penting. Dalam kode lthreaded, misalnya, dua nilai dalam satu baris cache akan menyebabkan warung pipa besar ketika dua utas mengaksesnya pada saat yang sama (tentu saja mengabaikan masalah keamanan utas).
greyfade
Kompiler C ++ dapat menyusun ulang bidang dalam kondisi tertentu saja, yang kemungkinan tidak terpenuhi jika Anda tidak mengetahui aturan tersebut. Selain itu, saya tidak mengetahui adanya kompiler C ++ yang benar-benar menggunakan kebebasan ini.
Sjoerd
1
Saya belum pernah melihat kompilasi memesan ulang bidang C. Saya telah melihat banyak insert padding dan alignment antara chars / ints misalnya ..
PaulHK
1

Seberapa penting penyelarasan memori? Apakah masih penting?

Iya. Tidak, itu tergantung.

Keluar dari sistem tertanam, kita sering memiliki sejumlah besar memori di komputer kita yang membuat manajemen memori jauh lebih sedikit kritik, saya sepenuhnya ke dalam optimasi, tetapi sekarang, apakah ini benar-benar sesuatu yang dapat membuat perbedaan jika kita membandingkan program yang sama dengan atau tanpa ingatannya ulang dan disejajarkan?

Aplikasi Anda akan memiliki jejak memori yang lebih kecil dan bekerja lebih cepat jika disejajarkan dengan benar. Dalam aplikasi desktop biasa, tidak masalah di luar kasus yang jarang terjadi / tidak biasa (seperti aplikasi Anda selalu berakhir dengan hambatan kinerja yang sama dan memerlukan optimasi). Artinya, aplikasi akan lebih kecil dan lebih cepat jika disejajarkan dengan benar, tetapi untuk sebagian besar kasus praktis seharusnya tidak mempengaruhi pengguna dengan satu atau lain cara.

Apakah penyelarasan memori memiliki kelebihan lain? Saya membaca di suatu tempat bahwa CPU bekerja lebih baik / lebih cepat dengan memori yang disejajarkan karena itu membutuhkan lebih sedikit instruksi untuk diproses (jika salah satu dari Anda memiliki tautan untuk artikel / benchmark tentang hal itu?), Dalam hal ini, apakah perbedaannya benar-benar signifikan? Apakah ada keuntungan lebih dari keduanya?

Itu bisa saja. Ini adalah sesuatu yang (mungkin) perlu diingat ketika menulis kode, tetapi dalam kebanyakan kasus itu seharusnya tidak masalah (yaitu, saya masih mengatur variabel anggota saya dengan jejak memori dan frekuensi akses - yang seharusnya memudahkan caching - tetapi saya melakukannya untuk kemudahan penggunaan / membaca dan refactoring kode, bukan untuk tujuan caching).

Pernahkah Anda tahu bagaimana penyelarasan memori bekerja tepat di C ++ karena tampaknya ada beberapa perbedaan?

Saya membaca tentang hal itu ketika hal-hal alignof keluar (C ++ 11?) Saya tidak peduli dengan itu sejak (saya melakukan sebagian besar aplikasi desktop dan pengembangan server backend hari ini).

utnapistim
sumber