Bagaimana cara satu utas berjalan pada banyak inti?

61

Saya mencoba memahami, pada tingkat tinggi, bagaimana satu utas berjalan di beberapa inti. Di bawah ini adalah pemahaman terbaik saya. Saya tidak percaya itu benar.

Berdasarkan bacaan saya tentang Hyper-threading , tampaknya OS mengatur instruksi semua utas sedemikian rupa sehingga mereka tidak saling menunggu. 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.

Jadi jika hanya ada satu utas, maka OS tidak akan melakukan optimasi. Namun, front-end CPU akan mendistribusikan set instruksi independen di antara masing-masing inti.

Menurut https://stackoverflow.com/a/15936270 , bahasa pemrograman tertentu dapat membuat lebih atau kurang utas, tetapi tidak relevan ketika menentukan apa yang harus dilakukan dengan utas tersebut. OS dan CPU menangani ini, jadi ini terjadi terlepas dari bahasa pemrograman yang digunakan.

masukkan deskripsi gambar di sini

Hanya untuk memperjelas, saya bertanya tentang menjalankan satu utas di beberapa inti, bukan tentang menjalankan beberapa utas pada satu inti.

Apa yang salah dengan ringkasan saya? Di mana dan bagaimana instruksi utas terpecah di antara beberapa inti? Apakah bahasa pemrograman itu penting? Saya tahu ini adalah topik yang luas; Saya berharap untuk pemahaman tingkat tinggi tentangnya.

Evorlor
sumber
6
Serangkaian instruksi untuk utas perangkat lunak tunggal dapat berjalan pada banyak inti, tetapi tidak sekaligus.
Kroltan
1
Anda sedang mencampur utas perangkat lunak (yang melibatkan penjadwal OS) dan utas perangkat keras atau HyperThreading (fitur CPU yang membuat satu inti berperilaku seperti dua).
ugoren
2
Saya memiliki 20 pengemudi dan 4 truk. Bagaimana mungkin satu pengemudi dapat mengirimkan paket dengan dua truk? Bagaimana mungkin satu truk dapat memiliki banyak pengemudi? Jawaban untuk kedua pertanyaan itu sama. Bergantian.
Eric Lippert

Jawaban:

84

Sistem operasi menawarkan irisan waktu CPU untuk utas yang memenuhi syarat untuk dijalankan.

Jika hanya ada satu inti, maka sistem operasi menjadwalkan utas yang paling memenuhi syarat untuk dijalankan pada inti itu untuk suatu irisan waktu. Setelah irisan waktu selesai, atau ketika utas berjalan memblokir pada IO, atau ketika prosesor terganggu oleh peristiwa eksternal, sistem operasi mengevaluasi ulang utas apa yang akan dijalankan berikutnya (dan itu dapat memilih utas yang sama lagi atau yang berbeda).

Kelayakan untuk menjalankan terdiri dari variasi pada keadilan dan prioritas dan kesiapan, dan dengan metode ini berbagai utas mendapatkan waktu, beberapa lebih dari yang lain.

Jika ada beberapa core, N, maka sistem operasi menjadwalkan thread N yang paling memenuhi syarat untuk dijalankan pada core.

Affinity Prosesor adalah pertimbangan efisiensi. Setiap kali CPU menjalankan utas yang berbeda dari sebelumnya, ia cenderung melambat sedikit karena cache-nya hangat untuk utas sebelumnya, tetapi dingin ke yang baru. Dengan demikian, menjalankan utas yang sama pada prosesor yang sama dalam berbagai waktu adalah keuntungan efisiensi.

Namun, sistem operasi bebas untuk menawarkan satu irisan waktu-ulir pada CPU yang berbeda, dan itu dapat berputar melalui semua CPU pada irisan waktu yang berbeda. Namun, itu tidak bisa, seperti yang dikatakan @ gnasher729 , menjalankan satu utas pada banyak CPU secara bersamaan.

Hyperthreading adalah metode di hardware dimana single ditingkatkan core CPU dapat mendukung pelaksanaan dua atau lebih yang berbeda thread secara bersamaan. (CPU semacam itu dapat menawarkan thread tambahan dengan biaya lebih rendah di real-estate silikon daripada core penuh tambahan.) Core CPU yang ditingkatkan ini perlu mendukung status tambahan untuk thread lain, seperti nilai register CPU, dan juga memiliki status koordinasi & perilaku yang memungkinkan pembagian unit-unit fungsional di dalam CPU tersebut tanpa menyatukan utas.

Hyperthreading, sementara secara teknis menantang dari perspektif perangkat keras, dari perspektif programmer, model eksekusi hanyalah model core CPU tambahan daripada yang lebih kompleks. Jadi, sistem operasi melihat core CPU tambahan, meskipun ada beberapa masalah afinitas prosesor baru karena beberapa thread hyperthreaded berbagi arsitektur cache satu core CPU.


Kita mungkin secara naif berpikir bahwa dua utas yang berjalan pada inti yang hiphread masing-masing berjalan setengah secepat masing-masing dengan inti penuh mereka sendiri. Tetapi ini belum tentu demikian, karena eksekusi satu thread penuh dengan siklus slack, dan beberapa jumlah dari mereka dapat digunakan oleh thread lain yang di-hyperhread. Lebih lanjut, bahkan selama siklus non-kendur, satu utas mungkin menggunakan unit fungsional yang berbeda dari yang lain sehingga eksekusi simultan dapat terjadi. CPU yang disempurnakan untuk hyperthreading mungkin memiliki beberapa unit fungsional tertentu yang banyak digunakan khusus untuk mendukungnya.

Erik Eidt
sumber
3
"Jadi, menjalankan utas yang sama pada prosesor yang sama dalam berbagai waktu adalah keuntungan efisiensi." Bukankah itu harus irisan waktu yang berdekatan ? Jika tidak, cache akan dihapus oleh utas lainnya, bukan? +1 untuk penjelasan yang bagus.
jpmc26
2
@Luaan: HT sering baik, tetapi situasinya tidak sesederhana seperti yang Anda gambarkan. Bandwidth masalah front-end (4 uops per jam pada Intel, 6 pada Ryzen) sama-sama dibagi di antara utas (kecuali satu terhenti). Jika itu hambatan, maka seperti saya katakan HT tidak akan membantu sama sekali. Bukan hal yang aneh bagi Skylake untuk mendekati itu dalam loop yang telah disetel dengan baik, jika ada campuran beban, ALU dan toko ... Transistor murah (dan tidak semua dapat beralih sekaligus atau CPU akan meleleh), jadi CPU x86 modern memiliki lebih banyak port eksekusi daripada yang dapat diberikan oleh front-end (dengan banyak unit eksekusi yang direplikasi ...
Peter Cordes
2
... pada banyak port) ... Ini mungkin tampak seperti pemborosan, tetapi seringkali satu loop hanya akan menggunakan satu jenis unit eksekusi ALU sekaligus, sehingga memiliki duplikat dari segala sesuatu berarti bahwa apa pun jenis kode yang sedang berjalan, ada banyak port untuk instruksinya. Jadi alasan Anda dikutip untuk mendapatkan manfaat dari HT tidak umum, karena sebagian besar kode memiliki beberapa beban dan / atau toko mengambil bandwidth front-end, dan apa yang tersisa sering tidak cukup untuk menjenuhkan unit eksekusi.
Peter Cordes
2
@Luaan: Juga, di Intel CPU, unit eksekusi integer dan FP / vektor berbagi port eksekusi yang sama . Sebagai contoh, FP FMA / mul / add units ada di port 0/1. Tetapi pengganda integer juga ada di port1, dan operasi integer sederhana dapat berjalan di salah satu dari 4 port eksekusi (diagram dalam jawaban saya). Utas kedua menggunakan bandwidth masalah akan memperlambat keduanya, bahkan jika mereka tidak bersaing untuk unit eksekusi, tetapi sering kali ada perolehan throughput bersih jika mereka tidak bersaing terlalu buruk untuk mendapatkan cache. Bahkan kode throughput tinggi yang disetel dengan baik seperti x264 / x265 (video encoders) bermanfaat sekitar 15% pada Skylake dari HT.
Peter Cordes
3
@luaan Selain apa yang Peter katakan, klaim Anda bahwa "Itu alasan asli di balik HT" tidak benar. Alasan asli di balik HT adalah bahwa mikroarsitektur NetBurst telah memperpanjang pipa sampai ke tingkat yang ekstrem (untuk tujuan menaikkan kecepatan clock) sehingga cabang salah duga dan gelembung pipa lainnya benar-benar mematikan kinerja. HT adalah salah satu solusi Intel untuk meminimalkan jumlah waktu yang digunakan unit eksekusi chip mahal ini menganggur karena gelembung dalam pipa: kode dari utas lain dapat dimasukkan dan dijalankan di lubang tersebut.
Cody Grey
24

Tidak ada yang namanya thread tunggal yang berjalan di beberapa core secara bersamaan.

Namun itu tidak berarti bahwa instruksi dari satu utas tidak dapat dijalankan secara paralel. Ada mekanisme yang disebut instruksi pipelining dan eksekusi out-of-order yang memungkinkannya. Setiap inti memiliki banyak sumber daya yang berlebihan yang tidak digunakan oleh instruksi sederhana, sehingga beberapa instruksi tersebut dapat dijalankan bersama (selama yang berikutnya tidak tergantung pada hasil sebelumnya). Namun, ini masih terjadi di dalam satu inti.

Hyper-threading adalah jenis varian ekstrim dari ide ini, di mana satu inti tidak hanya mengeksekusi instruksi dari satu thread secara paralel, tetapi juga mencampur instruksi dari dua thread yang berbeda untuk mengoptimalkan penggunaan sumber daya lebih jauh.

Entri Wikipedia terkait: Perpipaan instruksi , eksekusi out-of-order .

Frax
sumber
3
Mereka tidak bisa berjalan secara bersamaan, tetapi mereka bisa berjalan secara paralel? Bukankah ini hal yang sama?
Evorlor
10
@Evorlor Hal utama di sini adalah perbedaan antara inti dan unit eksekusi. Satu utas hanya dapat berjalan pada satu inti, tetapi sebuah prosesor dapat menggunakan analisis dinamis untuk mengetahui instruksi mana yang dieksekusi oleh inti tidak saling bergantung dan menjalankannya pada unit eksekusi yang berbeda secara bersamaan. Satu inti dapat memiliki beberapa unit eksekusi.
user1937198
3
@Evorlor: CPU yang rusak dapat menemukan dan mengeksploitasi paralelisme tingkat instruksi dalam aliran instruksi dari utas tunggal. mis. sering kali instruksi yang memperbarui penghitung loop independen dari beberapa pekerjaan lain yang dilakukan loop. Atau dalam satu a[i] = b[i] + c[i]lingkaran, setiap iterasi adalah independen, sehingga memuat, menambah, dan menyimpan dari iterasi yang berbeda dapat terbang sekaligus. Itu harus mempertahankan ilusi bahwa instruksi dieksekusi dalam urutan program, tetapi misalnya toko yang melewatkan dalam cache tidak menunda utas (sampai kehabisan ruang di buffer toko).
Peter Cordes
3
@ user1937198: Ungkapan "analisis dinamis" akan lebih sesuai dengan kompiler JIT. CPU yang rusak tidak benar - benar menganalisis; itu lebih seperti algoritma serakah yang menjalankan instruksi apa pun yang telah diterjemahkan & dikeluarkan dan memiliki input mereka siap. (Jendela pemesanan ulang yang tidak beraturan dibatasi oleh beberapa sumber daya mikroarsitektur, misalnya Intel Sandybridge memiliki ukuran Penyangga ReOrder sebesar 168 uops. Lihat juga mengukur ukuran ROB secara eksperimental ). Semua diimplementasikan dengan perangkat keras negara-mesin untuk menangani 4 uops per jam.
Peter Cordes
3
@Luaan ya, itu ide yang menarik, tetapi kompiler AOT masih tidak cukup pintar untuk sepenuhnya mengeksploitasinya. Juga, Linus Torvalds (dan lain-lain) berpendapat bahwa mengekspos bahwa banyak internal pipa merupakan kendala besar pada desain masa depan. mis. Anda tidak dapat benar-benar meningkatkan lebar pipa tanpa mengubah ISA. Atau Anda membangun CPU yang melacak dependensi dengan cara biasa, dan mungkin mengeluarkan dua grup VLIW secara paralel, tetapi kemudian Anda telah kehilangan manfaat kompleksitas CPU dari EPIC tetapi masih memiliki kelemahan (kehilangan masalah bandwidth ketika kompiler tidak dapat mengisi kata).
Peter Cordes
22

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 incinstruksi per jam jika semuanya independen. (Dengan latensi = 1, jadi Anda hanya perlu 4 register untuk memaksimalkan throughput dengan menjaga 4 incinstruksi 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 eaxatau edxdalam 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).

Jalur pipa penuh Haswell

Saya tahu ini sangat rumit. Artikel Kanter juga menunjukkan bagian-bagian ini untuk membicarakan tentang frontend secara terpisah dari unit eksekusi atau cache, misalnya.

Peter Cordes
sumber
2
"Menemukan dan mengeksploitasi paralelisme (tingkat instruksi) dalam program berulir tunggal dilakukan murni dalam perangkat keras" Perhatikan bahwa ini hanya berlaku untuk SPA tradisional, bukan VLIW di mana ILP ditentukan sepenuhnya oleh kompiler atau pemrogram, atau secara kooperatif antar perangkat keras dan perangkat lunak.
Hadi Brais
1
@ user7813604: ya. Hyperthreading tidak dapat memparalelkan satu utas. Ia melakukan sebaliknya: menjalankan beberapa utas pada satu inti, mengurangi kinerja per-utas tetapi meningkatkan throughput keseluruhan.
Peter Cordes
1
@ user7813604: Inti dari ILP adalah menemukan instruksi mana yang dapat dijalankan secara paralel sambil tetap mempertahankan ilusi bahwa setiap instruksi berjalan secara berurutan, masing-masing selesai sebelum yang berikutnya dimulai. CPU skalar pipelined mungkin perlu untuk menunda kadang-kadang untuk dependensi jika latency lebih tinggi dari 1. Tapi itu adalah kesepakatan yang lebih besar untuk CPU superscalar.
Peter Cordes
1
@ user7813604: ya, jawaban saya benar-benar menggunakannya sebagai contoh. Haswell misalnya dapat menjalankan hingga 4 incinstruksi dalam siklus clock yang sama, ke 4 unit eksekusi ALU integernya.
Peter Cordes
1
@ user7813604: Ya, ILP adalah berapa banyak yang dapat dieksekusi secara paralel. CPU yang sesungguhnya akan memiliki kemampuan terbatas untuk menemukan dan mengeksploitasi ILP dengan benar-benar menjalankannya secara paralel dalam satu inti, misalnya superscalar hingga 4-lebar di Intel. Jawaban ini mencoba menjelaskannya dengan contoh.
Peter Cordes