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.
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.
Jawaban:
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.
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 ++) dix86 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 / latency
dan 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.
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 add
mungkin 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_add
akan dikompilasi ke lock add
(atau lock xadd
jika nilai kembali digunakan), tetapi algoritma menggunakan CAS untuk melakukan sesuatu yang tidak dapat dilakukan dengan lock
instruksi ed biasanya bukan bencana. Gunakan C ++ 11std::atomic
atau C11 stdatomic
bukannya gcc warisan __sync
built-in atau yang lebih baru __atomic
built-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 union
peretasan 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/defrag
mendapatkan 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 perf
sebagian besar telah digantikan oprofile
. Untuk detail acara khusus untuk mikroarsitektur tertentu, gunakan ocperf.py
pembungkus . 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? .
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.