Ini membuat saya bertanya-tanya seberapa pentingkah Multithreading dalam skenario industri saat ini?
Dalam bidang yang sangat kritis terhadap kinerja di mana kinerja tidak berasal dari kode pihak ketiga yang melakukan pekerjaan berat, tetapi milik kita sendiri, maka saya akan cenderung mempertimbangkan hal-hal dalam urutan kepentingan ini dari perspektif CPU (GPU adalah wildcard yang saya menangkan) akan masuk ke:)
- Efisiensi Memori (mis: lokalitas referensi).
- Algoritma
- Multithreading
- SIMD
- Pengoptimalan Lainnya (petunjuk prediksi cabang statis, mis.)
Perhatikan bahwa daftar ini tidak semata-mata didasarkan pada kepentingan tetapi banyak dinamika lain seperti dampaknya terhadap pemeliharaan, seberapa mudahnya (jika tidak, patut dipertimbangkan lebih dulu), interaksinya dengan orang lain dalam daftar, dll.
Efisiensi Memori
Sebagian besar mungkin akan terkejut dengan pilihan saya efisiensi memori lebih dari algoritmik. Itu karena efisiensi memori berinteraksi dengan semua 4 item lain dalam daftar ini, dan itu karena pertimbangannya sering sangat banyak dalam kategori "desain" daripada kategori "implementasi". Memang ada sedikit masalah ayam atau telur di sini karena memahami efisiensi memori sering kali perlu mempertimbangkan semua 4 item dalam daftar, sementara semua 4 item lainnya juga memerlukan efisiensi memori. Namun itu adalah jantung dari segalanya.
Sebagai contoh, jika kita memiliki kebutuhan untuk struktur data yang menawarkan akses sekuensial linear-waktu dan penyisipan waktu-konstan ke belakang dan tidak ada yang lain untuk elemen kecil, pilihan naif di sini untuk dijangkau adalah daftar tertaut. Itu mengabaikan efisiensi memori. Ketika kita mempertimbangkan efisiensi memori dalam campuran, maka kita akhirnya memilih struktur yang lebih berdekatan dalam skenario ini, seperti struktur berbasis array yang dapat ditumbuhkan atau lebih banyak node yang berdekatan (mis: satu menyimpan 128 elemen dalam sebuah node) yang dihubungkan bersama, atau setidaknya daftar tertaut yang didukung oleh pengalokasi kumpulan. Ini memiliki keunggulan dramatis meskipun memiliki kompleksitas algoritme yang sama. Demikian juga, kita sering memilih quicksort dari array over merge sort meskipun kompleksitas algoritmiknya lebih rendah hanya karena efisiensi memori.
Demikian juga, kita tidak dapat memiliki multithreading yang efisien jika pola akses memori kita begitu granular dan tersebar di alam sehingga kita akhirnya memaksimalkan jumlah berbagi salah sambil mengunci pada tingkat yang paling rinci dalam kode. Jadi efisiensi memori mengalikan efisiensi multithreading. Ini merupakan prasyarat untuk mendapatkan yang terbaik dari utas.
Setiap item di atas dalam daftar memiliki interaksi yang kompleks dengan data, dan berfokus pada bagaimana data diwakili pada akhirnya berada di jalur efisiensi memori. Setiap satu dari hal di atas dapat dihambat dengan cara yang tidak tepat untuk mewakili atau mengakses data.
Alasan lain efisiensi memori sangat penting adalah dapat diterapkan di seluruh basis kode. Secara umum ketika orang membayangkan bahwa ketidakefisienan terakumulasi dari bagian-bagian kecil pekerjaan di sana-sini, itu adalah tanda bahwa mereka perlu mengambil profiler. Namun bidang latensi rendah atau yang berhubungan dengan perangkat keras yang sangat terbatas akan benar-benar menemukan, bahkan setelah pembuatan profil, sesi yang menunjukkan tidak ada hotspot yang jelas (hanya kali tersebar di semua tempat) dalam basis kode yang sangat tidak efisien dengan cara mengalokasikan, menyalin, dan mengakses memori. Biasanya ini adalah satu-satunya waktu seluruh basis kode dapat rentan terhadap masalah kinerja yang mungkin mengarah pada serangkaian standar baru yang diterapkan di seluruh basis kode, dan efisiensi memori sering menjadi inti dari itu.
Algoritma
Yang ini cukup banyak diberikan, karena pilihan dalam algoritme pengurutan dapat membuat perbedaan antara input besar yang membutuhkan waktu berbulan-bulan untuk disortir dibandingkan detik untuk disortir. Itu membuat dampak terbesar dari semua jika pilihannya adalah antara, katakanlah, benar-benar algoritma kuadratik atau kubik sub-par dan yang linearitmik, atau antara linear dan logaritmik atau konstan, setidaknya sampai kita memiliki seperti 1.000.000 mesin inti (dalam hal ini memori efisiensi akan menjadi lebih penting).
Namun, ini bukan di bagian atas daftar pribadi saya, karena siapa pun yang kompeten di bidangnya akan tahu untuk menggunakan struktur akselerasi untuk pemusnahan frustum, mis. Kita dipenuhi oleh pengetahuan algoritmik, dan mengetahui hal-hal seperti menggunakan varian dari trie seperti pohon radix untuk pencarian berbasis awalan adalah barang bayi. Kurangnya pengetahuan dasar semacam ini dari bidang yang sedang kami tangani, maka efisiensi algoritme tentu akan naik ke atas, tetapi seringkali efisiensi algoritmik sepele.
Juga menciptakan algoritma baru dapat menjadi kebutuhan di beberapa bidang (mis: dalam pemrosesan mesh saya harus menemukan ratusan karena mereka tidak ada sebelumnya, atau implementasi fitur serupa di produk lain adalah rahasia kepemilikan, tidak dipublikasikan dalam makalah ). Namun, begitu kita melewati bagian pemecahan masalah dan menemukan cara untuk mendapatkan hasil yang benar, dan begitu efisiensi menjadi tujuan, satu-satunya cara untuk benar-benar memperolehnya adalah dengan mempertimbangkan bagaimana kita berinteraksi dengan data (memori). Tanpa memahami efisiensi memori, algoritma baru dapat menjadi rumit tanpa perlu dengan upaya sia-sia untuk membuatnya lebih cepat, ketika satu-satunya hal yang diperlukan adalah sedikit lebih banyak pertimbangan efisiensi memori untuk menghasilkan algoritma yang lebih sederhana, lebih elegan.
Terakhir, algoritma cenderung lebih dalam kategori "implementasi" daripada efisiensi memori. Mereka sering lebih mudah untuk ditingkatkan di belakang bahkan dengan algoritma sub-optimal yang digunakan pada awalnya. Sebagai contoh, algoritma pemrosesan gambar yang lebih rendah sering hanya diimplementasikan di satu tempat lokal dalam basis kode. Itu bisa ditukar dengan yang lebih baik nanti. Namun, jika semua algoritma pemrosesan gambar terikat pada Pixel
antarmuka yang memiliki representasi memori sub-optimal, tetapi satu-satunya cara untuk memperbaikinya adalah mengubah cara beberapa piksel ditampilkan (dan bukan satu), maka kita sering SOL dan harus sepenuhnya menulis ulang basis kode keImage
antarmuka. Hal yang sama berlaku untuk mengganti algoritme pengurutan - biasanya detail implementasi, sementara perubahan lengkap untuk representasi data yang mendasarinya sedang diurutkan atau cara itu melewati pesan mungkin memerlukan antarmuka yang harus dirancang ulang.
Multithreading
Multithreading adalah yang sulit dalam konteks kinerja karena ini adalah optimasi tingkat mikro yang memainkan karakteristik perangkat keras, tetapi perangkat keras kami benar-benar meningkatkan ke arah itu. Saya sudah memiliki teman sebaya yang memiliki 32 core (saya hanya punya 4).
Namun mulithreading adalah salah satu optimasi mikro paling berbahaya yang mungkin diketahui oleh seorang profesional jika tujuannya digunakan untuk mempercepat perangkat lunak. Kondisi balapan adalah bug paling mematikan yang mungkin terjadi, karena sifatnya sangat tidak pasti (mungkin hanya muncul setiap beberapa bulan sekali pada mesin pengembang pada waktu yang paling tidak nyaman di luar konteks debugging, jika memang ada). Jadi itu bisa dibilang degradasi paling negatif pada rawatan dan potensi kebenaran kode di antara semua ini, terutama karena bug yang terkait dengan multithreading dapat dengan mudah terbang di bawah radar bahkan pengujian yang paling hati-hati.
Namun demikian, ini menjadi sangat penting. Walaupun mungkin masih tidak selalu mengalahkan sesuatu seperti efisiensi memori (yang kadang-kadang dapat membuat segalanya seratus kali lebih cepat) mengingat jumlah core yang kita miliki sekarang, kita melihat semakin banyak core. Tentu saja, bahkan dengan mesin 100-core, saya masih menempatkan efisiensi memori di bagian atas daftar, karena efisiensi benang umumnya tidak mungkin tanpanya. Suatu program dapat menggunakan seratus utas pada mesin seperti itu dan masih lambat tanpa representasi memori yang efisien dan pola akses (yang akan mengikat pola penguncian).
SIMD
SIMD juga agak canggung karena register sebenarnya semakin lebar, dengan rencana untuk menjadi lebih luas. Awalnya kami melihat register 64-bit MMX diikuti oleh 128-bit register XMM yang mampu melakukan 4 operasi SPFP secara paralel. Sekarang kita melihat register YMM 256-bit yang mampu 8 secara paralel. Dan sudah ada rencana untuk register 512-bit yang akan memungkinkan 16 secara paralel.
Ini akan berinteraksi dan berkembang biak dengan efisiensi multithreading. Namun SIMD dapat menurunkan pemeliharaan seperti halnya multithreading. Meskipun bug yang terkait dengannya tidak selalu sulit untuk direproduksi dan diperbaiki seperti kondisi deadlock atau ras, portabilitasnya canggung, dan memastikan bahwa kode dapat berjalan di mesin semua orang (dan menggunakan instruksi yang sesuai berdasarkan kemampuan perangkat keras mereka) adalah canggung.
Hal lain adalah bahwa sementara kompiler hari ini biasanya tidak mengalahkan kode SIMD yang ditulis secara ahli, mereka mengalahkan upaya naif dengan mudah. Mereka mungkin meningkat ke titik di mana kita tidak lagi harus melakukannya secara manual, atau setidaknya tanpa menjadi manual untuk menulis intrinsik atau kode perakitan langsung (mungkin hanya sedikit panduan manusia).
Sekali lagi, tanpa tata letak memori yang efisien untuk pemrosesan vektor, SIMD tidak berguna. Kami akhirnya hanya memuat satu bidang skalar ke register lebar hanya untuk melakukan satu operasi di atasnya. Inti dari semua item ini adalah ketergantungan pada tata letak memori agar benar-benar efisien.
Optimalisasi Lainnya
Inilah yang sering saya sarankan agar kita mulai memanggil "mikro" saat ini jika kata tersebut menyarankan tidak hanya melampaui fokus algoritmik tetapi juga terhadap perubahan yang memiliki dampak sangat kecil terhadap kinerja.
Sering mencoba untuk mengoptimalkan prediksi cabang memerlukan perubahan dalam algoritma atau efisiensi memori, misalnya Jika ini dicoba hanya melalui petunjuk dan menata ulang kode untuk prediksi statis, yang hanya cenderung meningkatkan pelaksanaan pertama kali kode semacam itu, membuat efek dipertanyakan jika tidak sering langsung diabaikan.
Kembali ke Multithreading untuk Kinerja
Jadi, seberapa pentingkah multithreading dari konteks kinerja? Pada mesin 4-core saya, idealnya dapat membuat hal-hal sekitar 5 kali lebih cepat (apa yang bisa saya dapatkan dengan hyperthreading). Akan jauh lebih penting bagi kolega saya yang memiliki 32 core. Dan itu akan menjadi semakin penting di tahun-tahun mendatang.
Jadi ini sangat penting. Tapi tidak ada gunanya hanya melemparkan seutas benang ke masalah jika efisiensi memori tidak ada untuk memungkinkan kunci digunakan hemat, untuk mengurangi kesalahan berbagi, dll.
Multithreading Diluar Kinerja
Multithreading tidak selalu tentang kinerja semata-mata dalam arti throughput langsung. Kadang-kadang digunakan untuk menyeimbangkan beban bahkan pada biaya throughput yang mungkin untuk meningkatkan daya tanggap kepada pengguna, atau untuk memungkinkan pengguna untuk melakukan lebih banyak tugas multitasking tanpa menunggu hal-hal selesai (mis: melanjutkan menjelajah saat mengunduh file).
Dalam kasus-kasus itu, saya menyarankan bahwa multithreading naik lebih tinggi ke atas (mungkin bahkan di atas efisiensi memori), karena ini tentang desain pengguna-akhir daripada tentang mendapatkan hasil maksimal dari perangkat keras. Ini akan sering mendominasi desain antarmuka dan cara kita menyusun seluruh basis kode dalam skenario seperti itu.
Ketika kita tidak hanya memparalelkan loop ketat mengakses struktur data besar, multithreading pergi ke kategori "desain" yang sangat hardcore, dan desain selalu mengalahkan implementasi.
Jadi dalam kasus tersebut, saya akan mengatakan mempertimbangkan multithreading dimuka sangat penting, bahkan lebih dari representasi memori dan akses.