rangkuman: Menemukan dan mengeksploitasi paralelisme (tingkat instruksi) dalam program berulir tunggal dilakukan murni dalam perangkat keras, oleh inti CPU yang digunakan. Dan hanya melalui beberapa ratus instruksi, bukan pemesanan ulang skala besar.
Program single-threaded tidak mendapatkan manfaat dari CPU multi-core, kecuali bahwa hal - hal lain dapat berjalan pada core lain alih-alih mengambil waktu jauh dari tugas single-threaded.
OS mengatur instruksi semua utas sedemikian rupa sehingga mereka tidak menunggu satu sama lain.
OS TIDAK melihat ke dalam aliran instruksi dari thread. Itu hanya menjadwalkan utas ke inti.
Sebenarnya, setiap core menjalankan fungsi scheduler OS ketika perlu mencari tahu apa yang harus dilakukan selanjutnya. Penjadwalan adalah algoritma terdistribusi. Untuk lebih memahami mesin multi-core, anggap setiap core menjalankan kernel secara terpisah. Sama seperti program multi-utas, kernel ditulis sehingga kodenya pada satu inti dapat dengan aman berinteraksi dengan kodenya di inti lain untuk memperbarui struktur data bersama (seperti daftar utas yang siap dijalankan.
Bagaimanapun, OS terlibat dalam membantu proses multi-threaded mengeksploitasi paralelisme level-thread yang harus diekspos secara eksplisit dengan secara manual menulis program multi-threaded . (Atau oleh kompilator penjajaran otomatis dengan OpenMP atau sesuatu).
Kemudian front-end CPU mengatur lebih lanjut instruksi-instruksi tersebut dengan mendistribusikan satu utas untuk setiap inti, dan mendistribusikan instruksi independen dari setiap utas di antara siklus terbuka apa pun.
Inti CPU hanya menjalankan satu aliran instruksi, jika tidak dihentikan (tertidur hingga interupsi berikutnya, mis. Interupsi timer). Seringkali itu adalah utas, tetapi bisa juga berupa penangan interrupt kernel, atau kode kernel lain-lain jika kernel memutuskan untuk melakukan sesuatu selain hanya kembali ke utas sebelumnya setelah menangani dan mengganggu atau panggilan sistem.
Dengan HyperThreading atau desain SMT lainnya, inti CPU fisik berfungsi seperti beberapa inti "logis". Satu-satunya perbedaan dari perspektif OS antara CPU quad-core-dengan-hyperthreading (4c8t) dan mesin 8-core (8c8t) adalah bahwa OS yang sadar HT akan mencoba menjadwalkan utas untuk memisahkan inti fisik sehingga mereka tidak perlu t bersaing satu sama lain. Sebuah OS yang tidak tahu tentang hyperthreading hanya akan melihat 8 core (kecuali Anda menonaktifkan HT di BIOS, maka itu hanya akan mendeteksi 4).
Istilah " front-end" mengacu pada bagian inti CPU yang mengambil kode mesin, menerjemahkan instruksi, dan mengeluarkannya ke bagian inti yang tidak sesuai pesanan . Setiap inti memiliki front-end sendiri, dan itu bagian dari inti secara keseluruhan. Instruksi yang diambil adalah apa yang sedang dijalankan CPU.
Di dalam bagian inti yang tidak sesuai pesanan, instruksi (atau uops) dikirim ke port eksekusi ketika operand input mereka siap dan ada port eksekusi gratis. Ini tidak harus terjadi dalam urutan program, jadi ini adalah bagaimana CPU OOO dapat mengeksploitasi paralelisme tingkat instruksi dalam satu utas .
Jika Anda mengganti "inti" dengan "unit eksekusi" dalam ide Anda, Anda hampir benar. Ya, CPU mendistribusikan instruksi independen / uops ke unit eksekusi secara paralel. (Tapi ada campuran istilah, karena Anda mengatakan "front-end" padahal sebenarnya itu adalah scheduler instruksi-CPU alias Stasiun Reservasi yang mengambil instruksi yang siap dieksekusi).
Eksekusi out-of-order hanya dapat menemukan ILP di tingkat yang sangat lokal, hanya hingga beberapa ratus instruksi, bukan antara dua loop independen (kecuali mereka pendek).
Sebagai contoh, ASM setara dengan ini
int i=0,j=0;
do {
i++;
j++;
} while(42);
akan berjalan secepat loop yang sama hanya menambah satu penghitung pada Intel Haswell. i++
hanya tergantung pada nilai sebelumnya i
, sementara j++
hanya tergantung pada nilai sebelumnya j
, sehingga dua rantai dependensi dapat berjalan secara paralel tanpa merusak ilusi segala sesuatu yang dieksekusi dalam urutan program.
Pada x86, loop akan terlihat seperti ini:
top_of_loop:
inc eax
inc edx
jmp .loop
Haswell memiliki 4 port eksekusi integer, dan semuanya memiliki unit adder, sehingga dapat mempertahankan throughput hingga 4 inc
instruksi per jam jika semuanya independen. (Dengan latensi = 1, jadi Anda hanya perlu 4 register untuk memaksimalkan throughput dengan menjaga 4 inc
instruksi dalam penerbangan. Bandingkan dengan vektor-FP MUL atau FMA: latensi = 5 throughput = 0,5 membutuhkan 10 vektor akumulator untuk menjaga 10 FMA dalam penerbangan untuk memaksimalkan throughput. Dan setiap vektor bisa 256b, menampung 8 float presisi tunggal).
Cabang yang diambil juga merupakan hambatan: loop selalu membutuhkan setidaknya satu jam penuh per iterasi, karena throughput cabang yang diambil terbatas pada 1 per jam. Saya bisa memasukkan satu instruksi lagi ke dalam loop tanpa mengurangi kinerja, kecuali itu juga membaca / menulis eax
atau edx
dalam hal ini akan memperpanjang rantai ketergantungan. Meletakkan 2 instruksi lebih banyak di loop (atau satu instruksi multi-uop yang kompleks) akan membuat hambatan di front-end, karena hanya dapat mengeluarkan 4 uops per jam ke dalam core out-of-order. (Lihat T&J SO ini untuk beberapa perincian tentang apa yang terjadi untuk loop yang bukan kelipatan dari 4 uops: loop-buffer dan cache uop membuat hal-hal menarik.)
Dalam kasus yang lebih kompleks, menemukan paralelisme membutuhkan melihat jendela instruksi yang lebih besar . (mis. mungkin ada urutan 10 instruksi yang semuanya tergantung satu sama lain, kemudian beberapa yang independen).
Kapasitas Re-Order Buffer adalah salah satu faktor yang membatasi ukuran jendela out-of-order. Di Intel Haswell, ini 192 uops. (Dan Anda bahkan dapat mengukurnya secara eksperimental , bersama dengan kapasitas pengubahan nama register (ukuran file register).) Core CPU berdaya rendah seperti ARM memiliki ukuran ROB yang jauh lebih kecil, jika mereka melakukan eksekusi yang tidak sesuai pesanan sama sekali.
Perhatikan juga bahwa CPU perlu disalin, serta rusak. Jadi ia harus mengambil & mendekode instruksi dengan baik sebelum yang dieksekusi, lebih disukai dengan throughput yang cukup untuk mengisi ulang buffer setelah melewatkan siklus pengambilan. Cabang-cabang itu rumit, karena kita tidak tahu ke mana harus mengambilnya jika kita tidak tahu ke mana cabang itu pergi. Inilah sebabnya mengapa prediksi cabang sangat penting. (Dan mengapa CPU modern menggunakan eksekusi spekulatif: mereka menebak ke arah mana cabang akan pergi dan mulai mengambil / mendekode / mengeksekusi aliran instruksi tersebut. Ketika kesalahan prediksi terdeteksi, mereka memutar kembali ke kondisi baik-terakhir yang diketahui dan mengeksekusi dari sana.)
Jika Anda ingin membaca lebih lanjut tentang internal CPU, ada beberapa tautan di wiki tag Stackoverflow x86 , termasuk ke panduan microarch Agner Fog , dan ke tulisan lengkap David Kanter dengan diagram Intel dan AMD CPU. Dari penulisan mikroarsitektur Intel Haswell-nya , ini adalah diagram terakhir dari seluruh pipa dari inti Haswell (bukan seluruh chip).
Ini adalah diagram blok dari inti CPU tunggal . CPU quad-core memiliki 4 di chip, masing-masing dengan cache L1 / L2 mereka sendiri (berbagi cache L3, pengontrol memori, dan koneksi PCIe ke perangkat sistem).
Saya tahu ini sangat rumit. Artikel Kanter juga menunjukkan bagian-bagian ini untuk membicarakan tentang frontend secara terpisah dari unit eksekusi atau cache, misalnya.