Ketika perhitungan terbatas bandwidth memori dilakukan dalam lingkungan memori bersama (mis. Berulir melalui OpenMP, Pthreads, atau TBB), ada dilema tentang bagaimana memastikan bahwa memori didistribusikan dengan benar di seluruh memori fisik , sehingga masing-masing thread kebanyakan mengakses memori pada sebuah bus memori "lokal". Meskipun antarmuka tidak portabel, sebagian besar sistem operasi memiliki cara untuk mengatur afinitas utas (mis. pthread_setaffinity_np()
Pada banyak sistem POSIX, sched_setaffinity()
di Linux, SetThreadAffinityMask()
pada Windows). Ada juga perpustakaan seperti hwloc untuk menentukan hirarki memori, tetapi sayangnya, sebagian besar sistem operasi belum menyediakan cara untuk mengatur kebijakan memori NUMA. Linux adalah pengecualian penting, dengan libnumamemungkinkan aplikasi untuk memanipulasi kebijakan memori dan migrasi halaman pada granularity halaman (dalam arus utama sejak 2004, dengan demikian tersedia secara luas). Sistem operasi lain mengharapkan pengguna untuk mematuhi kebijakan "sentuhan pertama" implisit.
Bekerja dengan kebijakan "sentuhan pertama" berarti bahwa penelepon harus membuat dan mendistribusikan utas dengan afinitas apa pun yang mereka rencanakan untuk digunakan nanti ketika pertama kali menulis ke memori yang baru dialokasikan. (Sangat sedikit sistem yang dikonfigurasikan sehingga malloc()
benar - benar menemukan halaman, itu hanya menjanjikan untuk menemukan mereka ketika mereka benar-benar rusak, mungkin oleh utas berbeda.) Ini menyiratkan bahwa alokasi menggunakan calloc()
atau segera menginisialisasi memori setelah alokasi menggunakan memset()
berbahaya karena akan cenderung untuk kesalahan semua memori ke bus memori inti menjalankan utas pengalokasian, yang mengarah ke bandwidth memori terburuk saat memori diakses dari banyak utas. Hal yang sama berlaku untuk new
operator C ++ yang bersikeras menginisialisasi banyak alokasi baru (misstd::complex
). Beberapa pengamatan tentang lingkungan ini:
- Alokasi dapat dibuat "thread threaded", tetapi sekarang alokasi menjadi dicampur ke dalam model threading yang tidak diinginkan untuk perpustakaan yang mungkin harus berinteraksi dengan klien menggunakan model threading yang berbeda (mungkin masing-masing dengan kumpulan thread mereka sendiri).
- RAII dianggap sebagai bagian penting dari C ++ idiomatik, tetapi tampaknya berbahaya secara aktif untuk kinerja memori di lingkungan NUMA. Penempatan
new
dapat digunakan dengan memori yang dialokasikan melaluimalloc()
atau dari rutinitaslibnuma
, tetapi ini mengubah proses alokasi (yang saya percaya perlu). - EDIT: Pernyataan saya sebelumnya tentang operator
new
tidak benar, itu dapat mendukung beberapa argumen, lihat balasan Chetan. Saya percaya masih ada kekhawatiran mendapatkan perpustakaan atau wadah STL untuk menggunakan afinitas yang ditentukan. Beberapa bidang dapat dikemas dan mungkin tidak nyaman untuk memastikan bahwa, misalnya,std::vector
realokasi dengan manajer konteks yang benar aktif. - Setiap utas dapat mengalokasikan dan kesalahan memori pribadinya sendiri, tetapi kemudian mengindeks ke daerah tetangga lebih rumit. (Pertimbangkan produk matriks-vektor yang jarang Anda dengan partisi baris dari matriks dan vektor; pengindeksan bagian yang tidak dimiliki memerlukan struktur data yang lebih rumit ketika tidak bersebelahan dalam memori virtual.)
Apakah ada solusi untuk alokasi / inisialisasi NUMA yang dianggap idiomatis? Sudahkah saya meninggalkan gotcha penting lainnya?
(Saya tidak bermaksud untuk C saya ++ contoh menyiratkan penekanan pada bahasa yang, namun C ++ bahasa mengkodekan beberapa keputusan tentang manajemen memori yang bahasa seperti C tidak, sehingga ada cenderung lebih tahan ketika menunjukkan bahwa C ++ programmer melakukan hal- hal-hal berbeda.)
sumber
Jawaban ini sebagai tanggapan atas dua kesalahpahaman terkait C ++ dalam pertanyaan.
Ini bukan jawaban langsung untuk masalah multi-core yang Anda sebutkan. Hanya menanggapi komentar yang mengklasifikasikan programmer C ++ sebagai C ++ fanatik sehingga reputasi tetap terjaga;).
Untuk titik 1. C ++ "baru" atau alokasi stack tidak bersikeras menginisialisasi objek baru, apakah POD atau tidak. Konstruktor default kelas, seperti yang didefinisikan oleh pengguna, memiliki tanggung jawab itu. Kode pertama di bawah ini menunjukkan sampah yang dicetak apakah kelasnya POD atau tidak.
Ke poin 2. C ++ memungkinkan overloading "baru" dengan beberapa argumen. Kode kedua di bawah ini menunjukkan kasus seperti itu untuk mengalokasikan objek tunggal. Itu harus memberikan ide dan mungkin berguna untuk situasi yang Anda miliki. operator baru [] dapat dimodifikasi dengan tepat juga.
// Kode untuk poin 1.
Kompiler Intel 11.1 menunjukkan output ini (yang tentu saja memori tidak diinisialisasi yang ditunjukkan oleh "a").
// Kode untuk poin 2.
sumber
std::complex
yang secara eksplisit diinisialisasi.std::complex
?Dalam kesepakatan. II kita punya infrastruktur perangkat lunak untuk memparalelkan perakitan pada setiap sel ke beberapa inti menggunakan Blok Bangunan Threading (pada dasarnya, Anda memiliki satu tugas per sel dan perlu menjadwalkan tugas-tugas ini ke prosesor yang tersedia - itu bukan bagaimana itu diimplementasikan tetapi itu adalah ide umum). Masalahnya adalah bahwa untuk integrasi lokal Anda memerlukan sejumlah objek sementara (awal) dan Anda harus menyediakan setidaknya sebanyak yang ada tugas yang dapat berjalan secara paralel. Kami melihat speedup yang buruk, mungkin karena ketika sebuah tugas diletakkan pada prosesor, ia mengambil salah satu objek awal yang biasanya akan berada di cache beberapa inti lainnya. Kami punya dua pertanyaan:
(i) Apakah ini benar-benar alasannya? Ketika kami menjalankan program di bawah cachegrind, saya melihat bahwa pada dasarnya saya menggunakan jumlah instruksi yang sama seperti ketika menjalankan program pada satu utas, namun total run-time yang diakumulasikan pada semua utas jauh lebih besar daripada yang satu-utas. Apakah ini benar-benar karena saya terus-menerus menyalahkan cache?
(ii) Bagaimana saya bisa mengetahui di mana saya berada, di mana masing-masing objek awal, dan objek awal mana yang harus saya ambil untuk mengakses yang panas di cache inti saya saat ini?
Pada akhirnya, kami belum menemukan jawaban untuk salah satu dari solusi ini dan setelah beberapa pekerjaan memutuskan bahwa kami tidak memiliki alat untuk menyelidiki dan menyelesaikan masalah ini. Saya tahu bagaimana setidaknya pada prinsipnya memecahkan masalah (ii) (yaitu, menggunakan objek thread-local, dengan asumsi bahwa thread tetap disematkan ke core prosesor - dugaan lain yang tidak mudah untuk diuji), tetapi saya tidak memiliki alat untuk menguji masalah (saya).
Jadi, dari sudut pandang kami, berurusan dengan NUMA masih merupakan pertanyaan yang belum terpecahkan.
sumber
Di luar hwloc ada beberapa alat yang dapat melaporkan lingkungan memori cluster HPC dan yang dapat digunakan untuk mengatur berbagai konfigurasi NUMA.
Saya akan merekomendasikan LIKWID sebagai salah satu alat seperti itu menghindari pendekatan berbasis kode yang memungkinkan Anda misalnya untuk pin proses ke inti. Pendekatan perkakas untuk mengatasi konfigurasi memori khusus mesin ini akan membantu memastikan portabilitas kode Anda di seluruh cluster.
Anda dapat menemukan presentasi singkat yang menguraikannya dari ISC'13 " LIKWID - Alat Kinerja Ringan " dan penulis telah menerbitkan makalah tentang Arxiv " Praktik terbaik untuk rekayasa kinerja berbantuan HPM pada prosesor multicore modern ". Makalah ini menjelaskan pendekatan untuk menafsirkan data dari penghitung perangkat keras untuk mengembangkan kode performan khusus untuk arsitektur dan topologi memori mesin Anda.
sumber