Apa Yang Harus Diketahui Setiap Pemrogram Tentang Memori?

164

Saya bertanya-tanya berapa banyak dari Ulrich Drepper tentang Apa Yang Harus Setiap Programmer Ketahui Tentang Memori dari 2007 masih valid. Juga saya tidak dapat menemukan versi yang lebih baru dari 1.0 atau errata.

Framester
sumber
1
apakah ada yang tahu jika saya dapat mengunduh artikel ini dalam format mobi di suatu tempat sehingga saya dapat dengan mudah membacanya di kindle? "pdf" sangat sulit dibaca karena masalah dengan zoom / format
javapowered
1
Ini bukan mobi, tetapi LWN menjalankan koran sebagai satu set artikel yang lebih mudah dibaca di ponsel / tablet. Yang pertama adalah di lwn.net/Articles/250967
Nathan

Jawaban:

111

Sejauh yang saya ingat, konten Drepper menggambarkan konsep dasar tentang memori: cara kerja cache CPU, memori fisik dan virtual, dan bagaimana kernel Linux menangani kebun binatang itu. Mungkin ada referensi API usang dalam beberapa contoh, tetapi itu tidak masalah; itu tidak akan mempengaruhi relevansi konsep dasar.

Jadi, setiap buku atau artikel yang menggambarkan sesuatu yang mendasar tidak bisa disebut ketinggalan jaman. "Apa yang harus diketahui oleh setiap programmer tentang memori" jelas layak dibaca, tetapi, saya kira itu bukan untuk "setiap programmer". Ini lebih cocok untuk system / embedded / kernel guys.

Dan Kruchinin
sumber
3
Ya saya benar-benar tidak mengerti mengapa seorang programmer harus tahu bagaimana SRAM dan DRAM bekerja pada level analog - itu tidak akan banyak membantu ketika menulis program. Dan orang-orang yang benar-benar membutuhkan pengetahuan itu, lebih baik menghabiskan waktu membaca manual tentang detail tentang waktu yang sebenarnya, dll. Tetapi bagi orang-orang yang tertarik pada hal-hal tingkat rendah HW? Mungkin tidak bermanfaat, tapi setidaknya menghibur.
Voo
47
Saat ini kinerja == kinerja memori, jadi memahami memori adalah hal yang paling penting dalam aplikasi kinerja tinggi apa pun. Ini membuat makalah ini penting bagi siapa pun yang terlibat dalam: pengembangan game, komputasi ilmiah, keuangan, basis data, penyusun, pemrosesan kumpulan data besar, visualisasi, apa pun yang harus menangani banyak permintaan ... Jadi ya, jika Anda bekerja dalam suatu aplikasi sebagian besar waktu itu tidak digunakan, seperti editor teks, makalahnya benar-benar tidak menarik sampai Anda perlu melakukan sesuatu dengan cepat seperti menemukan kata, menghitung kata-kata, periksa ejaan ... oh tunggu ... tidak apa-apa.
gnzlbg
144

Panduan dalam bentuk PDF ada di https://www.akkadia.org/drepper/cpumemory.pdf .

Secara umum masih sangat baik dan sangat direkomendasikan (oleh saya, dan saya pikir oleh ahli penyetelan kinerja lainnya). Akan keren jika Ulrich (atau siapa pun) menulis pembaruan 2017, tetapi itu akan banyak pekerjaan (misalnya menjalankan kembali tolok ukur). Lihat juga pengaturan kinerja x86 lainnya dan tautan optimisasi SSE / asm (dan C / C ++) di beri tag wiki . (Artikel Ulrich tidak spesifik x86, tetapi sebagian besar (semua) tolok ukurnya ada pada perangkat keras x86.)

Detail perangkat keras tingkat rendah tentang cara kerja DRAM dan cache masih berlaku . DDR4 menggunakan perintah yang sama seperti yang dijelaskan untuk DDR1 / DDR2 (baca / tulis burst). Perbaikan DDR3 / 4 bukanlah perubahan mendasar. AFAIK, semua hal independen lengkung masih berlaku secara umum, misalnya untuk AArch64 / ARM32.

Lihat juga bagian Latency Bound Platforms dari jawaban ini untuk perincian penting tentang efek memori / L3 latensi pada bandwidth single-threaded:, bandwidth <= max_concurrency / latencydan ini sebenarnya merupakan hambatan utama untuk bandwidth single-threaded pada CPU banyak-core modern seperti Xeon . Tapi desktop Skylake quad-core dapat mendekati memaksimalkan bandwidth DRAM dengan satu utas. Tautan itu memiliki beberapa info yang sangat bagus tentang toko NT vs. toko normal di x86. Mengapa Skylake jauh lebih baik daripada Broadwell-E untuk throughput memori single-threaded? adalah ringkasan.

Jadi saran Ulrich di 6.5.8 Memanfaatkan Semua Bandwidth tentang menggunakan memori jarak jauh pada NUMA node lain serta Anda sendiri, adalah kontra-produktif pada perangkat keras modern di mana pengontrol memori memiliki lebih banyak bandwidth daripada yang dapat digunakan oleh satu inti. Mungkin Anda dapat membayangkan sebuah situasi di mana ada manfaat bersih untuk menjalankan beberapa thread yang haus memori pada node NUMA yang sama untuk komunikasi antar-latensi rendah, tetapi meminta mereka menggunakan memori jarak jauh untuk bandwidth tinggi yang tidak sensitif terhadap latensi. Tapi ini sangat tidak jelas, biasanya hanya membagi utas antara NUMA dan minta mereka menggunakan memori lokal. Bandwidth per-core sensitif terhadap latensi karena batas max-concurrency (lihat di bawah), tetapi semua inti dalam satu soket biasanya dapat melebihi saturasi pengontrol memori dalam soket itu.


(biasanya) Jangan menggunakan prefetch perangkat lunak

Satu hal utama yang berubah adalah bahwa prefetch perangkat keras jauh lebih baik daripada pada Pentium 4 dan dapat mengenali pola akses tersendat hingga langkah yang cukup besar, dan beberapa aliran sekaligus (misalnya satu maju / mundur per halaman 4k). Manual optimasi Intel menjelaskan beberapa detail prefetcher HW di berbagai level cache untuk arsitektur mikro keluarga Sandybridge. Ivybridge dan yang lebih baru memiliki prefetch perangkat keras di halaman berikutnya, alih-alih menunggu cache hilang di halaman baru untuk memicu proses yang cepat dimulai. Saya berasumsi AMD memiliki beberapa hal serupa dalam manual optimasi mereka. Berhati-hatilah karena manual Intel juga penuh dengan saran lama, beberapa di antaranya hanya baik untuk P4. Bagian Sandybridge-spesifik tentu saja akurat untuk SnB, tetapi misalnyaun-laminasi u-micro-fused berubah dalam HSW dan manual tidak menyebutkannya .

Saran yang umum hari ini adalah untuk menghapus semua prefetch SW dari kode lama , dan hanya mempertimbangkan memasukkannya kembali jika profiling menunjukkan cache yang hilang (dan Anda tidak menjenuhkan bandwidth memori). Mengambil kedua sisi dari langkah selanjutnya dari pencarian biner masih dapat membantu. mis. begitu Anda memutuskan elemen mana yang akan dilihat berikutnya, ambil elemen 1/4 dan 3/4 sehingga elemen tersebut dapat dimuat secara paralel dengan memuat / memeriksa bagian tengah.

Saran untuk menggunakan thread prefetch terpisah (6.3.4) benar-benar usang , saya pikir, dan hanya baik pada Pentium 4. P4 memiliki hyperthreading (2 core logis berbagi satu inti fisik), tetapi tidak cukup jejak-cache (dan / atau sumber daya eksekusi out-of-order) untuk mendapatkan throughput menjalankan dua utas komputasi penuh pada inti yang sama. Tetapi CPU modern (Sandybridge-family dan Ryzen) jauh lebih tangguh dan harus menjalankan thread nyata atau tidak menggunakan hyperthreading (biarkan core core lainnya menganggur sehingga solo thread memiliki sumber daya penuh alih-alih mempartisi ROB).

Prefetch perangkat lunak selalu "rapuh" : angka tuning ajaib yang tepat untuk mendapatkan kecepatan tergantung pada detail perangkat keras, dan mungkin beban sistem. Terlalu dini dan terusir sebelum permintaan meningkat. Terlambat dan itu tidak membantu. Artikel blog ini menunjukkan kode + grafik untuk percobaan yang menarik dalam menggunakan prefetch SW pada Haswell untuk mengambil bagian non-sekuensial dari suatu masalah. Lihat juga Bagaimana cara menggunakan instruksi prefetch dengan benar? . Prefetch NT menarik, tetapi bahkan lebih rapuh karena penggusuran awal dari L1 berarti Anda harus pergi ke L3 atau DRAM, bukan hanya L2. Jika Anda membutuhkan setiap tetes kinerja terakhir, dan Anda dapat menyetel untuk mesin tertentu, prefetch SW patut dicari untuk akses berurutan, tetapimungkin masih melambat jika Anda memiliki cukup pekerjaan ALU yang harus dilakukan saat mendekati kemacetan pada memori.


Ukuran garis cache masih 64 byte. (L1D membaca / menulis bandwidth sangat tinggi, dan CPU modern dapat melakukan 2 beban vektor per jam + 1 toko vektor jika semuanya mengenai L1D. Lihat Bagaimana cache bisa secepat itu? ). Dengan AVX512, ukuran garis = lebar vektor, sehingga Anda dapat memuat / menyimpan seluruh baris cache dalam satu instruksi. Dengan demikian setiap load / store yang tidak selaras melintasi batas cache-line, bukan yang lain untuk 256b AVX1 / AVX2, yang sering tidak memperlambat perulangan pada array yang tidak ada dalam L1D.

Instruksi pemuatan yang tidak selaras memiliki penalti nol jika alamat disejajarkan pada saat runtime, tetapi kompiler (terutama gcc) membuat kode yang lebih baik ketika melakukan autovektorat jika mereka tahu tentang jaminan penyelarasan. Sebenarnya operasi yang tidak selaras umumnya cepat, tetapi pemisahan halaman masih terasa sakit (apalagi pada Skylake, meskipun; hanya ~ 11 siklus tambahan latensi vs 100, tetapi masih penalti throughput).


Seperti yang diramalkan Ulrich, setiap sistem multi-socket adalah NUMA dewasa ini: pengontrol memori terintegrasi adalah standar, yaitu tidak ada Northbridge eksternal. Tetapi SMP tidak lagi berarti multi-socket, karena CPU multi-core tersebar luas. CPU Intel dari Nehalem hingga Skylake telah menggunakan cache L3 inklusif yang besar sebagai backstop untuk koherensi antar core. CPU AMD berbeda, tapi saya tidak jelas detailnya.

Skylake-X (AVX512) tidak lagi memiliki L3 inklusif, tapi saya pikir masih ada direktori tag yang memungkinkannya memeriksa apa yang di-cache di mana saja pada chip (dan jika demikian di mana) tanpa benar-benar menyiarkan pengintaian ke semua core. Sayangnya SKX menggunakan mesh daripada ring bus , dengan latensi yang umumnya lebih buruk daripada Xeon banyak-core sebelumnya, sayangnya.

Pada dasarnya semua saran tentang mengoptimalkan penempatan memori masih berlaku, hanya detail dari apa yang terjadi ketika Anda tidak dapat menghindari kesalahan cache atau pertentangan bervariasi.


6.4.2 Operasi atom : patokan yang menunjukkan loop coba-ulang CAS sebagai 4x lebih buruk dari perangkat keras-arbitrasi lock addmungkin masih mencerminkan kasus pertikaian maksimum . Tetapi dalam program multi-thread nyata, sinkronisasi dijaga agar tetap minimum (karena mahal), sehingga pertikaian rendah dan loop coba-ulang CAS biasanya berhasil tanpa harus coba lagi.

C ++ 11 std::atomic fetch_addakan dikompilasi ke lock add(atau lock xaddjika nilai kembali digunakan), tetapi algoritma menggunakan CAS untuk melakukan sesuatu yang tidak dapat dilakukan dengan lockinstruksi ed biasanya bukan bencana. Gunakan C ++ 11std::atomic atau C11 stdatomicbukannya gcc warisan __syncbuilt-in atau yang lebih baru __atomicbuilt-in kecuali jika Anda ingin mencampur akses atom dan non-atom ke lokasi yang sama ...

8.1 DWCAS ( cmpxchg16b) : Anda dapat membujuk gcc agar memancarkannya, tetapi jika Anda ingin memuat efisien hanya satu setengah dari objek, Anda perlu unionperetasan jelek : Bagaimana saya bisa menerapkan penghitung ABA dengan c ++ 11 CAS? . (Jangan bingung DWCAS dengan DCAS dari 2 lokasi memori yang terpisah . Emulasi atom DCAS bebas-kunci tidak dimungkinkan dengan DWCAS, tetapi memori transaksional (seperti x86 TSX) memungkinkannya.)

8.2.4 memori transaksional : Setelah beberapa kesalahan dimulai (dirilis kemudian dinonaktifkan oleh pembaruan mikrokode karena bug yang jarang dipicu), Intel telah menjalankan memori transaksional di Broadwell model akhir dan semua CPU Skylake. Desainnya masih seperti yang digambarkan David Kanter untuk Haswell . Ada cara ellision kunci untuk menggunakannya untuk mempercepat kode yang menggunakan (dan dapat kembali ke) kunci biasa (terutama dengan kunci tunggal untuk semua elemen wadah sehingga banyak utas di bagian kritis yang sama sering tidak bertabrakan ), atau untuk menulis kode yang mengetahui tentang transaksi secara langsung.


7.5 Hugepages : hugepage transparan anonim berfungsi dengan baik di Linux tanpa harus menggunakan hugetlbfs secara manual. Buat alokasi> = 2MiB dengan penyelarasan 2MiB (mis. posix_memalign, Ataualigned_alloc yang tidak memaksakan persyaratan ISO C ++ 17 yang bodoh gagal saat size % alignment != 0).

Alokasi anonim 2MiB yang selaras akan menggunakan hugepage secara default. Beberapa beban kerja (misalnya yang tetap menggunakan alokasi besar untuk sementara waktu setelah membuatnya) mungkin mendapat manfaat dari
echo always >/sys/kernel/mm/transparent_hugepage/defragmendapatkan kernel untuk mendefrag memori fisik kapan pun diperlukan, alih-alih kembali ke halaman 4k. (Lihat dokumentasi kernel ). Atau, gunakan madvise(MADV_HUGEPAGE)setelah membuat alokasi besar (lebih disukai masih dengan penyelarasan 2MiB).


Lampiran B: Oprofile : Linux perfsebagian besar telah digantikan oprofile. Untuk detail acara khusus untuk mikroarsitektur tertentu, gunakan ocperf.pypembungkus . misalnya

ocperf.py stat -etask-clock,context-switches,cpu-migrations,page-faults,cycles,\
branches,branch-misses,instructions,uops_issued.any,\
uops_executed.thread,idq_uops_not_delivered.core -r2 ./a.out

Untuk beberapa contoh menggunakannya, lihat Bisakah MOV x86 benar-benar "bebas"? Mengapa saya tidak bisa mereproduksi ini sama sekali? .

Peter Cordes
sumber
3
Jawaban dan petunjuk yang sangat instruktif! Ini jelas layak mendapat lebih banyak suara!
claf
@Peter Cordes Apakah ada panduan / makalah lain yang Anda rekomendasikan untuk dibaca? Saya bukan programmer kinerja tinggi tetapi saya ingin belajar lebih banyak tentang hal itu dan mudah-mudahan mengambil praktik yang dapat saya masukkan ke dalam program harian saya.
user3927312
4
@ user3927312: agner.org/optimize adalah salah satu panduan terbaik dan paling koheren untuk hal-hal tingkat rendah untuk x86 secara khusus, tetapi beberapa ide umum berlaku untuk ISA lainnya. Selain panduan asm, Agner juga mengoptimalkan C ++ PDF. Untuk tautan kinerja / arsitektur-CPU lainnya, lihat stackoverflow.com/tags/x86/info . Saya juga menulis beberapa tentang mengoptimalkan C ++ dengan membantu kompiler membuat ASM yang lebih baik untuk loop kritis ketika ada baiknya melihat output asm kompiler: kode C ++ untuk menguji dugaan Collatz lebih cepat daripada asm yang ditulis tangan?
Peter Cordes
74

Dari sekilas pandang saya, tampilannya cukup akurat. Satu hal yang perlu diperhatikan, adalah porsi perbedaan antara pengontrol memori "terintegrasi" dan "eksternal". Sejak rilis Intel i7 line semuanya terintegrasi, dan AMD telah menggunakan pengontrol memori terintegrasi sejak chip AMD64 pertama kali dirilis.

Karena artikel ini ditulis, tidak banyak yang berubah, kecepatan semakin tinggi, pengontrol memori menjadi jauh lebih cerdas (i7 akan menunda menulis ke RAM sampai rasanya ingin melakukan perubahan), tetapi tidak banyak perubahan yang terjadi. . Setidaknya tidak dengan cara apa pun yang akan dipedulikan oleh pengembang perangkat lunak.

Timothy Baldridge
sumber
5
Saya ingin menerima Anda berdua. Tapi saya telah membatalkan posting Anda.
Framester
5
Mungkin perubahan paling utama yang relevan bagi pengembang SW adalah bahwa prefetch thread adalah ide yang buruk. CPU cukup kuat untuk menjalankan 2 utas lengkap dengan hyperthreading, dan memiliki prefetch HW yang jauh lebih baik. Preferensi SW secara umum jauh kurang penting, terutama untuk akses berurutan. Lihat jawaban saya.
Peter Cordes