BAB
BAB 3 3 Anas Anas Sofyan Sofyan Azhar Azhar SPSP
Diokta redho lastin Diokta redho lastin Ifan Faizal Adnan Ifan Faizal Adnan
Paralelisme Tingkat Instruksi: Konsep dan Tantangan Paralelisme Tingkat Instruksi: Konsep dan Tantangan
Semua prosesor sejak sekitar tahun 1985 menggunakan pipelining agar tumpang Semua prosesor sejak sekitar tahun 1985 menggunakan pipelining agar tumpang tindih dengan pelaksanaan instruksi dan peningkatan kinerja. Potensi tumpang tindih tindih dengan pelaksanaan instruksi dan peningkatan kinerja. Potensi tumpang tindih antara instruksi ini disebut instruction-level parallelism (ILP), karena instruksinya dapat antara instruksi ini disebut instruction-level parallelism (ILP), karena instruksinya dapat dievaluasi secara paralel. Dalam bab ini serta Lampiran H, kita melihat sejauh mana dievaluasi secara paralel. Dalam bab ini serta Lampiran H, kita melihat sejauh mana tenknologi untuk memperluas pipelining dasar dengan meningkatkan jumlah tenknologi untuk memperluas pipelining dasar dengan meningkatkan jumlah paralelisme yang dieksploitasi di antara instruksi.
paralelisme yang dieksploitasi di antara instruksi.
Bab ini berada pada tingkat yang jauh lebih maju daripada materi tentang Bab ini berada pada tingkat yang jauh lebih maju daripada materi tentang pipelining dasar pada Lampiran C. Jika Anda t
pipelining dasar pada Lampiran C. Jika Anda tidak benar-benar memahami gagasan diidak benar-benar memahami gagasan di Lampiran C, Anda harus meninjau kembali lampiran tersebut sebelum memasuki bab Lampiran C, Anda harus meninjau kembali lampiran tersebut sebelum memasuki bab ini.
ini.
Kita memulai bab ini dengan melihat keterbatasan yang dipaksakan oleh data Kita memulai bab ini dengan melihat keterbatasan yang dipaksakan oleh data dan pengendalian bahaya dan kemudian beralih ke topik untuk meningkatkan dan pengendalian bahaya dan kemudian beralih ke topik untuk meningkatkan kemampuan kompiler dan prosesor untuk mengeksploitasi paralelisme. Bagian ini kemampuan kompiler dan prosesor untuk mengeksploitasi paralelisme. Bagian ini mengenalkan sejumlah besar konsep, yang kita bangun di bab ini dan bab berikutnya. mengenalkan sejumlah besar konsep, yang kita bangun di bab ini dan bab berikutnya. Sementara beberapa bahan yang lebih mendasar dalam bab ini dapat dipahami tanpa Sementara beberapa bahan yang lebih mendasar dalam bab ini dapat dipahami tanpa adanya gagasan di dua bagian pertama, bahan dasar ini penting untuk bagian selanjutnya adanya gagasan di dua bagian pertama, bahan dasar ini penting untuk bagian selanjutnya dari bab ini.
dari bab ini.
Terdapat dua pendekatan yang dapat dipisahkan untuk dieksploitasi ILP: (1) Terdapat dua pendekatan yang dapat dipisahkan untuk dieksploitasi ILP: (1) pendekatan
pendekatan yang yang mengandalkan mengandalkan perangkat perangkat keras keras untuk untuk membantu membantu menemukan menemukan dandan mengeksploitasi paralelisme secara dinamis, dan (2) pendekatan yang bergantung pada mengeksploitasi paralelisme secara dinamis, dan (2) pendekatan yang bergantung pada teknologi perangkat lunak untuk menemukan paralelisme secara statis pada waktu teknologi perangkat lunak untuk menemukan paralelisme secara statis pada waktu kompilasi. Prosesor yang menggunakan dinamis, pendekatan berbasis hardware, kompilasi. Prosesor yang menggunakan dinamis, pendekatan berbasis hardware, termasuk seri Intel Core, mendominasi di pasar desktop
termasuk seri Intel Core, mendominasi di pasar desktop dan server. Di pasar perangkatdan server. Di pasar perangkat mobile pribadi, di mana efisiensi energi sering menjadi tujuan utama, perancang mobile pribadi, di mana efisiensi energi sering menjadi tujuan utama, perancang mengeksploitasi tingkat paralelisme tingkat instruksi yang lebih rendah. Jadi, pada mengeksploitasi tingkat paralelisme tingkat instruksi yang lebih rendah. Jadi, pada
tahun 2011, sebagian besar prosesor untuk pasar PMD menggunakan pendekatan statis, tahun 2011, sebagian besar prosesor untuk pasar PMD menggunakan pendekatan statis, seperti yang akan kita lihat di ARM Cortex-A8; Namun, prosesor masa depan (mis., seperti yang akan kita lihat di ARM Cortex-A8; Namun, prosesor masa depan (mis., ARM Cortex-A9 yang baru) menggunakan pendekatan dinamis. Pendekatan berbasis ARM Cortex-A9 yang baru) menggunakan pendekatan dinamis. Pendekatan berbasis kompilator yang agresif telah dicoba berkali-kali dimulai pada tahun 1980an dan yang kompilator yang agresif telah dicoba berkali-kali dimulai pada tahun 1980an dan yang terbaru dalam seri Intel Itanium. Meskipun banyak usaha, pendekatan semacam itu terbaru dalam seri Intel Itanium. Meskipun banyak usaha, pendekatan semacam itu belum berhasil di luar jangkauan aplikasi ilmiah yang sempit.
belum berhasil di luar jangkauan aplikasi ilmiah yang sempit.
Dalam beberapa tahun terakhir, banyak teknik yang dikembangkan untuk satu Dalam beberapa tahun terakhir, banyak teknik yang dikembangkan untuk satu pendekatan telah dieksploitasi
pendekatan telah dieksploitasi dalam desain yang terutama bergandalam desain yang terutama bergantung pada yang lain.tung pada yang lain. Bab ini memperkenalkan konsep dasar dan kedua pendekatan. Diskusi tentang Bab ini memperkenalkan konsep dasar dan kedua pendekatan. Diskusi tentang keterbatasan pendekatan ILP termasuk di dalam bab ini, dan itu adalah keterbatasan keterbatasan pendekatan ILP termasuk di dalam bab ini, dan itu adalah keterbatasan yang secara langsung mengarah ke pergerakan untuk multicore. Memahami yang secara langsung mengarah ke pergerakan untuk multicore. Memahami keterbatasan tetap penting dalam menyeimbangkan penggunaan ILP dan paralelisme keterbatasan tetap penting dalam menyeimbangkan penggunaan ILP dan paralelisme tingkat thread.
tingkat thread.
Pada bagian ini, kita membahas fitur dari kedua program dan prosesor yang Pada bagian ini, kita membahas fitur dari kedua program dan prosesor yang membatasi jumlah paralelisme yang dapat dieksploitasi di antara instruksi, serta membatasi jumlah paralelisme yang dapat dieksploitasi di antara instruksi, serta pemetaan kritis antara struktur program
pemetaan kritis antara struktur program dan struktur perangkat keras, dan struktur perangkat keras, yang merupakanyang merupakan kunci untuk memahami apakah properti program benar-benar akan membatasi Kinerja kunci untuk memahami apakah properti program benar-benar akan membatasi Kinerja dan dalam keadaan apa.
dan dalam keadaan apa. Nilai CP
Nilai CPI (siklus I (siklus per instruksi) per instruksi) untuk prosesor untuk prosesor pipelined adalah pipelined adalah jumlah CPI jumlah CPI dasardasar dan semua kontribusi dari stall:
dan semua kontribusi dari stall:
Pipelineline CPI = Pipelineline ideal CPI + Struktural stall + Hambatan bahaya Pipelineline CPI = Pipelineline ideal CPI + Struktural stall + Hambatan bahaya data + Kendala control
data + Kendala control
Gambar 3.1 Teknik utama yang diperiksa pada Lampiran C, Bab 3, dan Gambar 3.1 Teknik utama yang diperiksa pada Lampiran C, Bab 3, dan Lampiran H diperlihatkan bersamaan dengan komponen persamaan CPI yang Lampiran H diperlihatkan bersamaan dengan komponen persamaan CPI yang mempengaruhi teknik ini.
mempengaruhi teknik ini.
CPI pipeline ideal adalah ukuran kinerja maksimal yang dapat dicapai oleh CPI pipeline ideal adalah ukuran kinerja maksimal yang dapat dicapai oleh penerapannya.
penerapannya. Dengan Dengan mengurangi mengurangi masing-masing masing-masing persyaratan persyaratan dari dari sisi sisi kanan, kanan, kamikami mengurangi keseluruhan jalur pipeline CPI atau, sebaliknya, meningkatkan IPC mengurangi keseluruhan jalur pipeline CPI atau, sebaliknya, meningkatkan IPC (instruksi per jam). Persamaan di atas memungkinkan kita untuk mengkarakterisasi (instruksi per jam). Persamaan di atas memungkinkan kita untuk mengkarakterisasi berbagai
berbagai teknik teknik dengan dengan komponen komponen apa apa dari dari CPI CPI keseluruhan keseluruhan yang yang dikurangi dikurangi teknik.teknik. Gambar 3.1 menunjukkan teknik yang kami teliti dalam bab ini dan di Lampiran H, Gambar 3.1 menunjukkan teknik yang kami teliti dalam bab ini dan di Lampiran H,
serta topik yang dibahas dalam materi pengantar di Lampiran C. Dalam bab ini, kita serta topik yang dibahas dalam materi pengantar di Lampiran C. Dalam bab ini, kita akan melihat bahwa teknik yang kita perkenalkan
akan melihat bahwa teknik yang kita perkenalkan untuk mengurangi pipeline ideal CPIuntuk mengurangi pipeline ideal CPI dapat meningkatkan pentingnya mengatasi bahaya.
dapat meningkatkan pentingnya mengatasi bahaya.
Apa itu Instruction-Level Parallelism? Apa itu Instruction-Level Parallelism?
Semua teknik dalam bab ini mengeksploitasi paralelisme di antara instruksi. Semua teknik dalam bab ini mengeksploitasi paralelisme di antara instruksi. Jumlah paralelisme yang tersedia di dalam blok dasar - urutan kode garis lurus tanpa Jumlah paralelisme yang tersedia di dalam blok dasar - urutan kode garis lurus tanpa cabang kecuali untuk masuk dan tidak ada cabang kecuali di pintu keluar - cukup kecil. cabang kecuali untuk masuk dan tidak ada cabang kecuali di pintu keluar - cukup kecil. Untuk program MIPS yang khas, frekuensi cabang dinamis rata-rata seringkali antara Untuk program MIPS yang khas, frekuensi cabang dinamis rata-rata seringkali antara 15% dan 25%, yang berarti bahwa antara tiga dan enam instruksi dijalankan di antara 15% dan 25%, yang berarti bahwa antara tiga dan enam instruksi dijalankan di antara sepasang cabang. Karena petunjuk ini cenderung saling bergantung satu sama lain, sepasang cabang. Karena petunjuk ini cenderung saling bergantung satu sama lain, jumlah tumpang tindih
jumlah tumpang tindih yang dapat kita eksplyang dapat kita eksploitasi dalam blok dasar oitasi dalam blok dasar cenderung kurangcenderung kurang dari ukuran blok dasar rata-rata. Untuk mendapatkan peningkatan kinerja yang dari ukuran blok dasar rata-rata. Untuk mendapatkan peningkatan kinerja yang substansial, kita harus memanfaatkan ILP di b
substansial, kita harus memanfaatkan ILP di beberapa blok dasar.eberapa blok dasar.
Cara termudah dan paling umum untuk meningkatkan ILP adalah dengan Cara termudah dan paling umum untuk meningkatkan ILP adalah dengan memanfaatkan paralelisme di antara iterasi loop. Jenis paralelisme ini sering disebut memanfaatkan paralelisme di antara iterasi loop. Jenis paralelisme ini sering disebut loop-level parallelism. Berikut adalah contoh sederhana dari sebuah loop yang loop-level parallelism. Berikut adalah contoh sederhana dari sebuah loop yang menambahkan dua elemen 1000-elemen array dan benar-benar pararel:
menambahkan dua elemen 1000-elemen array dan benar-benar pararel: for (i=0; i<=999; i=i+1)
for (i=0; i<=999; i=i+1) x[i] = x[i] + y[i];
x[i] = x[i] + y[i];
Setiap iterasi loop bisa tumpang tindih dengan iterasi lainnya, meskipun dalam Setiap iterasi loop bisa tumpang tindih dengan iterasi lainnya, meskipun dalam setiap iterasi loop hanya ada sedikit atau tidak ada kesempatan untuk tumpang tindih. setiap iterasi loop hanya ada sedikit atau tidak ada kesempatan untuk tumpang tindih. Kami akan memeriksa sejumlah teknik untuk mengubah paralelisme tingkat berulang Kami akan memeriksa sejumlah teknik untuk mengubah paralelisme tingkat berulang menjadi paralelisme tingkat instruksi. Pada dasarnya, teknik semacam itu bekerja menjadi paralelisme tingkat instruksi. Pada dasarnya, teknik semacam itu bekerja dengan membuka gulungan loop secara statis oleh kompilator (seperti pada bagian dengan membuka gulungan loop secara statis oleh kompilator (seperti pada bagian berikutnya) atau secar
berikutnya) atau secara dinamis oleh a dinamis oleh perangkat keras (seperti perangkat keras (seperti pada Bagian 3.5 pada Bagian 3.5 dan 3.6).dan 3.6). Metode alternatif terpenting untuk mengeksploitasi paralelisme tingkat berulang Metode alternatif terpenting untuk mengeksploitasi paralelisme tingkat berulang adalah penggunaan SIMD pada kedua prosesor vektor dan Graphics Processing Units adalah penggunaan SIMD pada kedua prosesor vektor dan Graphics Processing Units (GPU), keduanya dibahas di Bab 4. Instruksi SIMD mengeksploitasi paralelisme tingkat (GPU), keduanya dibahas di Bab 4. Instruksi SIMD mengeksploitasi paralelisme tingkat data dengan beroperasi dari sejumlah kecil sampai cukup jumlah item data secara data dengan beroperasi dari sejumlah kecil sampai cukup jumlah item data secara
paralel (biasanya dua sampai delapan). Instruksi vektor mengeksploitasi paralelisme tingkat data dengan mengoperasikan banyak item data secara paralel dengan menggunakan unit eksekusi paralel dan pipeline dalam. Sebagai contoh, urutan kode di atas, yang dalam bentuk sederhana memerlukan tujuh instruksi per iterasi (dua muatan, add, store, dua update alamat, dan cabang) dengan total 7000 instruksi, mungkin dijalankan dalam seperempat sebanyak Instruksi di beberapa arsitektur SIMD dimana empat item data diproses sesuai instruksi. Pada beberapa prosesor vektor, urutan ini hanya memerlukan empat instruksi: dua instruksi untuk memuat vektor x dan y dari memori, satu instruksi untuk menambahkan dua vektor, dan sebuah instruksi untuk menyimpan kembali vektor hasil. Tentu saja, instruksi ini akan diberi pipelined dan memiliki latensi yang relatif panjang, namun latensi ini mungkin tumpang tindih.
Ketergantungan Data dan Bahaya
Menentukan bagaimana satu instruksi bergantung pada yang lain sangat penting untuk menentukan berapa banyak paralelisme ada dalam sebuah program dan bagaimana paralelisme tersebut dapat dieksploitasi. Secara khusus, untuk mengeksploitasi paralelisme tingkat instruksi kita harus menentukan instruksi mana yang dapat dieksekusi secara paralel. Jika dua instruksi sejajar, mereka dapat melakukan secara simultan dalam pipeline kedalaman yang sewenang-wenang tanpa menyebabkan stalls, dengan asumsi pipeline memiliki sumber daya yang memadai (dan karenanya tidak ada bahaya struktural). Jika dua instruksi saling bergantung, keduanya tidak paralel dan harus dieksekusi secara berurutan, walaupun keduanya mungkin sering tumpang tindih sebagian. Kunci dalam kedua kasus ini adalah untuk menentukan apakah sebuah instruksi bergantung pada instruksi lain.
Ketergantungan data
Ada tiga jenis Ketergantungan yang berbeda: Ketergantungan data (juga dis ebut Ketergantungan data sejati), ketergantungan nama, dan ketergantungan kontrol. Instruksi j adalah data bergantung pada instruksi i jika salah satu dari berikut ini b erlaku:
• Instruksi i menghasilkan sebuah hasil yang dapat digunakan dengan
instruksi j
• Instruksi j adalah data yang bergantung pada instruksi k, dan instruksi k
adalah data yang bergantung pada instruksi i.
Kondisi kedua hanya menyatakan bahwa satu instruksi bergantung pada yang lain jika ada rantai ketergantungan tipe pertama di antara kedua instruksi tersebut. Rantai ketergantungan ini bisa sepanjang seluruh program. Perhatikan bahwa ketergantungan dalam satu instruksi (seperti ADDD R1, R1, R1) tidak dianggap sebagai ketergantungan.
Sebagai contoh, perhatikan urutan kode MIPS berikut yang menambahkan sebuah vektor nilai pada memori (mulai dari 0 (R1) dan dengan elemen terakhir pada 8 (R2)) oleh skalar pada register F2. (Untuk kesederhanaan, sepanjang bab ini, contoh kita mengabaikan efek dari cabang yang tertunda.)
Loop: L.D F0,0(R1) ;F0=array element ADD.D F4,F0,F2 ;add scalar in F2
S.D F4,0(R1) ;store result
DADDUI R1,R1,#-8 ;decrement pointer 8 bytes BNE R1,R2,LOOP ;branch R1!=R2
Ketergantungan data dalam urutan kode ini melibatkan data floating-point:
Loop: L.D F0,0(R1) ;F0=array element ADD.D F4,F0,F2 ;add scalar in F2
S.D F4,0(R1) ;store result
Dan data bilangan bulat:
DADDIU R1,R1,#-8 ;decrement pointer ;8 bytes (per DW)
Dalam kedua urutan ketergantungan di atas, seperti yang ditunjukkan oleh tanda panah, setiap instruksi tergantung pada yang sebelumnya. Panah di sini dan dalam contoh berikut menunjukkan urutan yang harus dipertahankan untuk eksekusi yang benar. Titik panah dari instruksi yang harus mendahului instruksi yang ditunjukkan oleh panah.
Jika dua instruksi bergantung pada data, mereka harus mengeksekusi secara berurutan dan tidak dapat mengeksekusi secara bersamaan atau benar-benar tumpang tindih. Ketergantungan itu menyiratkan bahwa akan ada satu atau lebih bahaya data antara dua instruksi tersebut. (Lihat Lampiran C untuk deskripsi singkat tentang bahaya data, yang akan kita definisikan secara tepat di beberapa halaman.) Melaksanakan instruksi secara bersamaan akan menyebabkan prosesor dengan jaringan pipeline saling terkait (dan kedalaman pipeline lebih panjang dari jarak antara instruksi dalam siklus) ke Mendeteksi bahaya dan stall, sehingga mengurangi atau menghilangkan tumpang tindih. Dalam sebuah prosesor tanpa interlock yang bergantung pada penjadwalan kompilator, compiler tidak dapat menjadwalkan instruksi dependen sedemikian rupa sehingga keduanya benar-benar tumpang tindih, karena program tidak akan berjalan dengan benar. Adanya ketergantungan data dalam urutan instruksi mencerminkan ketergantungan data pada kode sumber dimana urutan instruksi dihasilkan. Efek dari ketergantungan data asli harus dipertahankan.
Ketergantungan adalah milik program. Entah akibat ketergantungan tertentu menyebabkan bahaya aktual terdeteksi dan apakah bahaya tersebut benar-benar menyebabkan stall merupakan properti dari organisasi pipeline. Perbedaan ini sangat penting untuk memahami bagaimana paralelisme tingkat instruksi dapat dieksploitasi.
Ketergantungan data mencakup tiga hal: (1) kemungkinan bahaya, (2) urutan hasil harus dihitung, dan (3) batas atas tentang seberapa banyak paralelisme dapat dieksploitasi. Batas tersebut dieksplorasi dalam Bagian 3.10 dan di Lampiran H secara lebih rinci.
Karena ketergantungan data dapat membatasi jumlah paralelisme tingkat instruksi yang dapat kita manfaatkan, fokus utama bab ini adalah mengatasi keterbatasan tersebut. Ketergantungan dapat diatasi dengan dua cara yang berbeda: (1) menjaga ketergantungan tapi menghindari bahaya, dan (2) menghilangkan
ketergantungan dengan mengubah kodenya. Penjadwalan kode adalah metode utama yang digunakan untuk menghindari bahaya tanpa mengubah ketergantungan, dan penjadwalan semacam itu dapat dilakukan oleh kompilator dan perangkat keras.
Sebuah nilai data dapat mengalir antara instruksi, baik melalui register atau melalui lokasi memori. Ketika arus data terjadi dalam register, mendeteksi ketergantungan sangatlah mudah karena nama register tetap dalam petunjuk, walaupun akan semakin rumit saat cabang melakukan intervensi dan masalah yang benar memaksa kompiler atau perangkat keras menjadi konservatif.
Ketergantungan yang mengalir melalui lokasi memori lebih sulit dideteksi, karena dua alamat dapat merujuk ke lokasi yang sama namun terlihat berbeda: Misalnya, 100 (R4) dan 20 (R6) mungkin merupakan alamat memori yang identik. Selain itu, alamat efektif dari sebuah loads atau penyimpanan dapat berubah dari satu pelaksanaan instruksi ke instruksi lainnya (sehingga 20 (R4) dan 20 (R4) mungkin berbeda), yang selanjutnya mempersulit deteksi ketergantungan.
Dalam bab ini, kami memeriksa perangkat keras untuk mendeteksi ketergantungan data yang melibatkan lokasi memori, namun kami akan melihat bahwa teknik ini juga memiliki keterbatasan. Teknik kompilator untuk mendeteksi ketergantungan semacam itu sangat penting dalam mengungkap paralelisme tingkat berulang.
Ketergantungan Nama
Jenis ketergantungan kedua adalah ketergantungan nama. Ketergantungan nama terjadi saat dua instruksi menggunakan register atau lokasi memori yang sama, disebut nama, namun tidak ada aliran data antara petunjuk yang terkait dengan nama itu. Ada dua jenis ketergantungan nama antara instruksi i yang mendahului instruksi j dalam urutan program:
1. Antidependensi antara instruksi i dan instruksi j terjadi saat instruksi j menulis register atau lokasi memori yang instruksi saya baca. Pemesanan asli harus dipelihara untuk memastikan bahwa saya membaca nilai yang
benar. Pada contoh di halaman 151, ada antidependensi antara S.D dan DADDIU pada register R1. 2.
2. Ketergantungan keluaran terjadi ketika instruksi i dan instruksi j menulis register atau lokasi memori yang sama. Pengurutan antara instruksi harus dipelihara untuk memastikan bahwa nilai yang akhirnya ditulis sesuai dengan instruksi j.
Dua ketergantungan (antidependensi dan keluaran) tersebut adalah ketergantungan nama, berlawanan dengan ketergantungan data yang sebenarnya, karena tidak ada nilai yang ditransmisikan antara petunjuk. Karena ketergantungan nama bukanlah ketergantungan yang benar, instruksi yang terlibat dalam ketergantungan nama dapat dijalankan secara bersamaan atau diatur ulang, jika nama (nomor register atau lokasi memori) yang digunakan dalam instruksi diubah sehingga petunjuknya tidak bertentangan.
Renaming ini bisa lebih mudah dilakukan untuk register operan, dimana itu disebut register renaming. Daftar renaming bisa dilakukan secara statis oleh kompilator atau secara dinamis oleh perangkat keras. Sebelum menjelaskan ketergantungan yang timbul dari cabang, mari kita periksa hubungan antara ketergantungan dan bahaya data pipeline.
Bahaya data
Bahaya ada bila ada ketergantungan nama atau data antar instruksi, dan cukup dekat sehingga tumpang tindih selama eksekusi akan mengubah urutan akses ke operan yang terlibat dalam ketergantungan. Karena ketergantungan, kita harus melestarikan apa yang disebut urutan program - yaitu perintah yang akan dieksekusi instruksi jika dijalankan secara berurutan satu per satu seperti yang ditentukan oleh program sumber asli. Tujuan kedua teknik perangkat lunak dan perangkat keras kami adalah untuk mengeksploitasi paralelisme dengan melestarikan urutan program hanya jika hal itu mempengaruhi hasil program. Mendeteksi dan menghindari bahaya memastikan agar pesanan program yang diperlukan tetap terjaga.
Bahaya data, yang dijelaskan secara informal dalam Lampiran C, dapat diklasifikasikan sebagai satu dari tiga jenis, tergantung pada urutan akses baca dan tulis dalam petunjuk. Dengan konvensi, bahaya dinamai dengan memesan dalam program yang harus dipelihara oleh jaringan pipeline. Pertimbangkan dua instruksi i dan j, dengan saya sebelum j dalam urutan program. Bahaya data yang mungkin adalah
• RAW (read after write) — j mencoba membaca sebuah sumber sebelum I
menulisnya, jadi j salah mendapat nilai lama. Bahaya ini adalah jenis yang paling umum dan sesuai dengan ketergantungan data yang sebenarnya.
Urutan program harus dipelihara untuk memastikan bahwa j menerima nilai dari i.
• WAW (write after write) — j mencoba menulis operan sebelum ditulis oleh i.
Penulisan akhirnya tampil dengan urutan yang salah, meninggalkan nilai yang ditulis oleh saya daripada nilai yang ditulis oleh j di tempat tujuan. Bahaya ini sesuai dengan ketergantungan output. Bahaya WAW hadir hanya di jaringan pipeline yang menulis di lebih dari satu tahap pipeline atau membiarkan instruksi berlanjut meskipun instruksi sebelumnya macet.
• WAR (write after read) — j mencoba menulis tujuan sebelum dibaca oleh i,
jadi i salah mendapat nilai baru. Bahaya ini timbul dari antidependensi (atau ketergantungan nama). Bahaya pesan tidak dapat terjadi di sebagian besar jaringan pipeline statis, bahkan jalur pipeline yang lebih dalam atau jalur pipeline terapung, karena semua dibaca lebih awal (dalam ID di saluran pipeline di Lampiran C) dan semua penulisannya terlambat (dalam bahasa WB dalam pipeline pada Lampiran C). Bahaya WAR terjadi baik bila ada beberapa instruksi yang menuliskan hasilnya di awal jalur instruksi dan petunjuk lainnya yang membaca sumber di akhir jalur pipeline, atau saat
instruksi disusun ulang, seperti yang akan kita lihat di bab ini.
Perhatikan bahwa kasus RAR (read after read) bukan merupakan bahaya.
Jenis ketergantungan terakhir adalah ketergantungan kontrol. Ketergantungan kontrol menentukan urutan instruksi, i, sehubungan dengan instruksi cabang sehingga instruksi saya dieksekusi dalam urutan program yang benar dan hanya jika seharusnya. Setiap instruksi, kecuali yang ada di blok program dasar pertama, bergantung pada beberapa cabang, dan pada umumnya ketergantungan kontrol ini harus dipelihara untuk
mempertahankan tatanan program. Salah satu contoh ketergantungan kontrol yang paling sederhana adalah ketergantungan pernyataan di bagian "lalu" dari pernyataan if
di cabang. Misalnya, di segmen kode if p1 {
S1; };if p2 { S2; }
S1 adalah kontrol yang bergantung pada p1, dan S2 bergantung pada p2 tapi tidak pada p1.
Secara umum, dua kendala dipaksakan oleh ketergantungan kontrol:
1. Instruksi yang dikendalikan bergantung pada cabang tidak dapat dipindahkan sebelum cabang sehingga eksekusi tidak lagi dikendalikan oleh cabang. Misalnya, kita tidak dapat mengambil instruksi dari bagian selanjutnya dari sebuah pernyataan jika dan memindahkannya sebelum pernyataan if.
2. Suatu instruksi yang tidak terkontrol bergantung pada cabang tidak dapat dipindahkan setelah cabang sehingga pelaksanaannya dikendalikan oleh cabang. Sebagai contoh, kita tidak dapat mengambil pernyataan sebelum pernyataan if dan memindahkannya ke bagian selanjutnya.
Ketika prosesor mempertahankan tatanan program yang ketat, mereka memastikan bahwa ketergantungan kontrol juga dipertahankan. Kita mungkin bersedia untuk mengeksekusi instruksi yang seharusnya tidak dijalankan, dengan demikian melanggar ketergantungan kontrol, jika kita dapat melakukannya tanpa mempengaruhi kebenaran program. Dengan demikian, ketergantungan kontrol bukanlah sifat kritis yang harus dilestarikan. Sebagai gantinya, dua sifat penting untuk ketepatan program -dan biasanya dipertahankan dengan mempertahankan ketergantungan data -dan kontrol - adalah perilaku pengecualian dan arus data.
Menjaga perilaku pengecualian berarti bahwa setiap perubahan dalam pemesanan eksekusi instruksi tidak boleh mengubah bagaimana pengecualian diajukan dalam program. Seringkali ini rileks berarti bahwa penataan ulang pelaksanaan instruksi tidak boleh menyebabkan pengecualian baru dalam program. Contoh sederhana menunjukkan bagaimana menjaga ketergantungan kontrol dan data dapat mencegah situasi seperti itu. Pertimbangkan urutan kode ini:
DADDU R2,R3,R4
BEQZ R2,L1
LW R1,0(R2)
L1:
Dalam kasus ini, mudah untuk melihat bahwa jika kita tidak menjaga ketergantungan data yang melibatkan R2, kita dapat mengubah hasil program. Yang kurang jelas adalah kenyataan bahwa jika kita mengabaikan ketergantungan kontrol dan memindahkan instruksi loads sebelum cabang, instruksi loads dapat menyebabkan pengecualian perlindungan memori. Perhatikan bahwa tidak ada ketergantungan data yang menghalangi kita untuk menukar BEQZ dan LW; Itu hanya ketergantungan kontrol. Agar kita dapat menyusun ulang petunjuk ini (dan tetap mempertahankan ketergantungan data), kami ingin mengabaikan pengecualian saat cabang diambil. Pada Bagian 3.6, kita akan melihat teknik perangkat keras, spekulasi, yang memungkinkan kita mengatasi masalah pengecualian ini. Lampiran H melihat teknik perangkat lunak untuk mendukung spekulasi.
Properti kedua yang dijaga dengan pemeliharaan ketergantungan data dan ketergantungan kontrol adalah arus data. Aliran data adalah aliran sebenarnya dari nilai data antar petunjuk yang menghasilkan hasil dan yang mengkonsumsinya. Cabang membuat arus data dinamis, karena memungkinkan sumber data agar mendapat instruksi dari banyak titik. Dengan kata lain, tidak cukup hanya untuk menjaga ketergantungan data, karena sebuah instruksi mungkin bergantung pada data lebih dari satu pendahulunya. Urutan program adalah apa yang menentukan pendahulu mana yang benar-benar akan memberikan nilai data ke sebuah instruksi. Urutan program dipastikan
Sebagai contoh, perhatikan fragmen kode berikut ini: DADDU R1,R2,R3 BEQZ R4,L DSUBU R1,R5,R6 L: ... OR R7,R1,R8
Dalam contoh ini, nilai R1 yang digunakan oleh instruksi OR tergantung pada apakah cabang diambil atau tidak. Ketergantungan data saja tidak cukup untuk menjaga kebenaran. Instruksi OR adalah data yang bergantung pada instruksi DADDU dan DSUBU, namun menjaga perintah itu saja tidak mencukupi untuk eksekusi yang benar. Sebagai gantinya, saat instruksi dijalankan, arus data harus dipelihara: Jika cabang tidak diambil, maka nilai R1 yang dihitung oleh DSUBU harus digunakan oleh OR, dan jika cabang diambil, nilai R1 dihitung. Oleh DADDU harus digunakan oleh OR. Dengan menjaga ketergantungan kontrol OR pada cabang, kita mencegah perubahan yang tidak sah terhadap arus data. Untuk alasan yang sama, instruksi DSUBU tidak dapat dipindahkan di atas cabang. Spekulasi, yang membantu dengan masalah pengecualian, juga akan memungkinkan kita mengurangi dampak ketergantungan kontrol sambil tetap menjaga arus data, seperti yang akan kita lihat di Bagian 3.6.
Terkadang kita bisa menentukan bahwa melanggar ketergantungan kontrol tidak dapat mempengaruhi perilaku pengecualian atau arus data. Perhatikan urutan kode berikut ini: DADDU R1,R2,R3 BEQZ R12,skip DSUBU R4,R5,R6 DADDU R5,R4,R9 skip: OR R7,R8,R9
Misalkan kita tahu bahwa tujuan register instruksi DSUBU (R4) tidak terpakai setelah instruksi dilabeli skip. (Properti apakah nilai akan digunakan oleh instruksi yang
akan datang disebut liveness.) Jika R4 tidak digunakan, maka ubah nilai R4 sesaat sebelum cabang tidak mempengaruhi arus data karena R4 akan mati (bukan hidup) Di wilayah kode setelah skip. Jadi, jika R4 sudah mati dan instruksi DSUBU yang ada tidak dapat menghasilkan pengecualian (selain dari prosesor yang memprosesnya sama), kita bisa memindahkan instruksi DSUBU sebelum cabang, karena aliran data tidak dapat terpengaruh oleh perubahan ini.
Jika cabang diambil, instruksi DSUBU akan dijalankan dan tidak ada gunanya, tapi tidak akan mempengaruhi hasil program. Jenis penjadwalan kode ini juga merupakan bentuk spekulasi, yang sering disebut spekulasi perangkat lunak, karena kompilator bertaruh pada hasil cabang; Dalam hal ini, taruhannya adalah bahwa cabang biasanya tidak diambil. Mekanisme spekulasi kompilator yang lebih ambisius dibahas pada Lampiran H. Biasanya, akan jelas bila kita mengatakan spekulasi atau spekulatif apakah mekanismenya adalah mekanisme perangkat keras atau perangkat lunak; Bila tidak jelas, yang terbaik adalah mengatakan "spekulasi perangkat keras" atau "spekulasi perangkat lunak."
Ketergantungan kontrol dipelihara dengan menerapkan deteksi bahaya kontrol yang menyebabkan stalls kontrol. Kendala kontrol dapat dieliminasi atau dikurangi dengan berbagai teknik perangkat keras dan perangkat lunak, yang ka mi teliti di Bagian 3.3.
Teknik Dasar Kompilator untuk Mengekspos ILP
Bagian ini membahas penggunaan teknologi kompilator sederhana untuk meningkatkan kemampuan prosesor untuk mengeksploitasi ILP. Teknik ini sangat penting bagi prosesor yang menggunakan static issue atau static scheduling. Berbekal teknologi kompilator ini, kami akan segera meneliti perancangan dan kinerja prosesor dengan menggunakan static issuing. Lampiran H akan menyelidiki kompiler yang lebih canggih dan skema perangkat keras terkait yang dirancang untuk memungkinkan prosesor mengeksploitasi lebih banyak paralelisme pengajaran.
Untuk menjaga pipeline tetap penuh, paralelisme di antara instruksi harus dieksploitasi dengan menemukan urutan instruksi yang tidak terkait yang dapat tumpang tindih dalam pipeline. Untuk menghindari stalls pipeline, eksekusi instruksi dependen harus dipisahkan dari instruksi sumber dengan jarak dalam siklus waktu yang sama dengan latensi pipeline dari instruksi sumber tersebut. Kemampuan kompilator untuk melakukan penjadwalan ini bergantung pada jumlah ILP yang tersedia dalam program dan pada tingkat latensi unit fungsional dalam pipeline. Gambar 3.2 menunjukkan latensi unit FP yang kita asumsikan dalam bab ini, kecuali latensi yang berbeda dinyatakan secara eksplisit. Kami mengasumsikan standar lima tahap pipeline integer, sehingga cabang memiliki penundaan satu siklus clock. Kami berasumsi bahwa unit fungsional sepenuhnya pipelined atau direplikasi (sebanyak kedalaman pipeline), sehingga operasi jenis apapun dapat dikeluarkan pada setiap siklus clock dan tidak ada bahaya struktural.
Dalam subbagian ini, kita melihat bagaimana compiler dapat meningkatkan jumlah ILP yang ada dengan mengubah loop(perulangan). Contoh ini berfungsi baik
untuk menggambarkan teknik penting dan juga untuk memotivasi transformasi program yang lebih hebat yang dijelaskan pada Lampiran H. Kami akan mengandalkan pada segmen kode berikut, yang menambahkan skalar ke vektor:
for (i=999; i>=0; i=i – 1)
x[i] = x[i] + s;
Kita dapat melihat bahwa loop ini sejajar dengan memperhatikan bahwa tubuh setiap iterasi bersifat independen. Kami meresmikan gagasan ini di Lampiran H dan menjelaskan bagaimana kita dapat menguji apakah pengulangan loop independen pada waktu kompilasi. Pertama, mari kita lihat kinerja loop ini, menunjukkan bagaimana kita dapat menggunakan paralelisme untuk meningkatkan kinerjanya pada jaringan pipeline MIPS dengan latensi yang ditunjukkan di atas.
Langkah pertama adalah menerjemahkan segmen di atas ke bahasa assembly MIPS. Pada segmen kode berikut, R1 pada awalnya adalah alamat elemen dalam array
dengan alamat tertinggi, dan F2 berisi nilai skalar s. Daftar R2 didahului, sehingga 8 (R2) adalah alamat dari elemen terakhir yang beroperasi.
Instruksi memproduksi hasil Intruksi menggunakanan hasil Latensi dalam siklus clock
FP ALU op FP ALU op lain 3 FP ALU op Store double 2
Load double FP ALU op 1 Load double Store double 0
Gambar 3.2 Latensi operasi FP yang digunakan dalam bab ini.
Kolom terakhir adalah jumlah siklus jam intervensi yang diperlukan untuk menghindari stals. Angka-angka ini mirip dengan latensi rata-rata yang akan kita lihat pada unit FP. Latensi muatan floating-point ke penyimpanan adalah 0, karena hasil loads dapat dilewati tanpa mengulur-ulur penyimpanan. Kami akan terus mengasumsikan latensi loads integer 1 dan latensi ALU integer 0.
Kode MIPS langsung, tidak dijadwalkan untuk pipeline, terlihat seperti ini: Loop: L.D F0,0(R1) ;F0=array element
ADD.D F4,F0,F2 ;add scalar in F2 S.D F4,0(R1) ;store result
DADDUI R1,R1,#-8 ;decrement pointer ;8 bytes (per DW) BNE R1,R2,Loop ;branch R1!=R2
Mari mulai dengan melihat seberapa baik loop ini akan berjalan saat dijadwalkan pada jalur pipeline sederhana untuk MIPS dengan latensi dari Gambar 3.2.
Contoh Tunjukkan bagaimana loop akan terlihat pada MIPS, keduanya terjadwal dan tidak terjadwal, termasuk stall atau siklus jam menganggur. Jadwalkan penundaan operasi floating-point, namun ingatlah bahwa kami mengabaikan cabang yang tertunda. Jawab Tanpa penjadwalan apapun, loop akan mengeksekusi sebagai berikut, mengambil sembilan siklus:
Siklus jam dikeluarkan
Loop: L.D F0,0(R1) 1
ADD.D F4,F0,F2 3 stall 4 stall 5 S.D F4,0(R1) 6 DADDUI R1,R1,#-8 7 stall 8 BNE R1,R2,Loop 9
Kita bisa menjadwalkan loop untuk mendapatkan hanya dua stalls dan mengurangi waktu menjadi tujuh siklus:
Loop: L.D F0,0(R1) DADDUI R1,R1,#-8 ADD.D F4,F0,F2 stall stall S.D F4,8(R1) BNE R1,R2,Loop
Stalls setelah ADD.D digunakan untuk S.D.
Pada contoh sebelumnya, kita menyelesaikan satu iterasi loop dan menyimpan satu elemen array setiap tujuh siklus clock, namun kerja sebenarnya dari operasi pada elemen array hanya membutuhkan tiga (loads, penambahan, dan penyimpanan) dari tujuh siklus clock tersebut. Siklus empat jam tersisa terdiri dari overhead loop-DADDUI dan BNE-dan dua stalls. Untuk menghilangkan empat siklus clock ini, kita perlu mendapatkan lebih banyak operasi dibandingkan dengan jumlah instruksi
overhead.
Skema sederhana untuk meningkatkan jumlah instruksi relatif terhadap instruksi cabang dan overhead adalah loop unrolling. Membuka gulungan hanya mereplikasi tubuh lingkaran beberapa kali, menyesuaikan kode penghentian loop.
Loop unrolling juga bisa digunakan untuk memperbaiki penjadwalan. Karena menghilangkan cabang, memungkinkan instruksi dari berbagai iterasi untuk dijadwalkan bersama. Dalam hal ini, kita bisa menghilangkan data menggunakan stalls dengan membuat tambahan instruksi independen di dalam body loop. Jika kita hanya mereplikasi instruksi saat membuka unrolled loop, penggunaan register yang sama dapat mencegah kita untuk secara efektif menjadwalkan loop. Dengan demikian, kita akan ingin menggunakan register yang berbeda untuk setiap iterasi, meningkatkan jumlah register yang dibutuhkan.
Contoh Tunjukkan loop yang tidak tergulung (unrolled loop) sehingga ada empat salinan dari isi loop, dengan asumsi R1 - R2 (yaitu ukuran array) pada awalnya merupakan kelipatan 32, yang berarti bahwa jumlah pengulangan loop adalah kelipatan dari 4. Hilangkan perhitungan yang sangat berlebihan dan jangan gunakan kembali pada register mana pun.
Answer Inilah hasilnya setelah menggabungkan instruksi DADDUI dan membuang operasi BNE, yang tidak perlukan, yang diduplikasi saat membuka gulungan. Perhatikan bahwa R2 yang sekarang harus disetel sehingga 32 (R2) adalah alamat awal dari empat elemen terakhir.
Loop: L.D F0,0(R1)
ADD.D F4,F0,F2
S.D F4,0(R1) ;drop DADDUI & BNE L.D F6,-8(R1)
ADD.D F8,F6,F2
S.D F8,-8(R1) ;drop DADDUI & BNE L.D F10,-16(R1)
ADD.D F12,F10,F2
S.D F12,-16(R1) ;drop DADDUI & BNE L.D F14,-24(R1)
ADD.D F16,F14,F2
S.D F16,-24(R1) DADDUI R1,R1,#-32
BNE R1,R2,Loop
Kita telah menghilangkan tiga cabang dan tiga deret R1. Alamat pada loads dan penyimpanan telah diberi kompensasi untuk memungkinkan instruksi DADDUI di R1
digabungkan. Pengoptimalan ini mungkin tampak sepele, tapi tidak demikian; Hal itu membutuhkan penggantian dan penyederhanaan simbolis. Substitusi simbolis dan penyederhanaan akan mengatur ulang ungkapan-ungkapan sehingga memungkinkan konstanta menjadi runtuh, memungkinkan ungkapan seperti ((i + 1) + 1) ditulis ulang sebagai (i + (1 + 1)) dan kemudian disederhanakan menjadi (i + 2). Kita akan melihat bentuk pengoptimalan yang lebih umum yang menghilangkan perhitungan dependen pada Lampiran H.
Tanpa penjadwalan, setiap operasi dalam unrolled loop diikuti oleh operasi yang dependen dan dengan demikian akan menyebabkan sebuah stalls. Lingkaran ini akan berjalan dalam 27 siklus clock - setiap LD memiliki 1 stall, masing-masing ADDD 2,
siklus instruksi DADDUI 1, plus 14 siklus instruksi instruksi - atau 6,75 jam untuk masing-masing dari empat elemen, namun dapat dijadwalkan untuk meningkatkan kinerja secara signifikan. Unrolling loop biasanya dilakukan di awal proses kompilasi, sehingga perhitungan berlebihan bisa terpapar dan dieliminasi oleh pengoptimasi.
Dalam program nyata kita biasanya tidak tahu batas atas pada loop. Misalkan n, dan kita ingin unrolling loop untuk membuat salinan dari isi. Alih-alih satu loop unrolled, kita menghasilkan sepasang loop berturut-turut. Waktu eksekusi pertama (n mod k) dan memiliki isi yang merupakan loop asli. Yang kedua adalah isi yang tidak tergali dikelilingi oleh lingkaran luar yang mengulangi (n / k) kali. (Seperti yang akan kita lihat di Bab 4, teknik ini serupa dengan teknik yang disebut penambangan strip, yang digunakan pada kompiler untuk prosesor vektor.) Untuk nilai n yang besar, sebagian besar waktu eksekusi akan dihabiskan di badan loop unrolled. Pada contoh sebelumnya, unrolling meningkatkan kinerja loop ini dengan menghilangkan instruksi overhead, meskipun meningkatkan ukuran kode secara substansial. Bagaimana kinerja loop yang tidak dibuka saat dijadwalkan untuk pipeline yang dijelaskan sebelumnya?
Contoh Tampilkan loop unrolled pada contoh sebelumnya setelah dijadwalkan untuk pipeline dengan latensi dari Gambar 3.2.
L.D F6,-8(R1) L.D F10,-16(R1) L.D F14,-24(R1) ADD.D F4,F0,F2 ADD.D F8,F6,F2 ADD.D F12,F10,F2 ADD.D F16,F14,F2 S.D F4,0(R1) S.D F8,-8(R1) DADDUI R1,R1,#-32 S.D F12,16(R1) S.D F16,8(R1) BNE R1,R2,Loop
Waktu eksekusi loop unrolled telah turun menjadi, total 14 clock cycle, atau 3,5 clock cycle per element, dibandingkan dengan 9 cycle per element sebelum ada unrolling atau penjadwalan dan 7 cycle saat terjadwal namun tidak dibuka.
bahkan keuntungan dari penjadwalan pada loop unrolled lebih besar dari pada loop asli. Peningkatan ini muncul karena membuka loop unrolling yang memperlihatkan lebih banyak perhitungan yang dapat dijadwalkan untuk meminimalkan stalls; Kode diatas tidak memiliki stalls. Penjadwalan loop dengan cara ini mengharuskan menyadari bahwa loads dan penyimpanan bersifat independen dan dapat dipertukarkan.
Ringkasan dari Loop Unrolling and Penjadwalan
Sepanjang bab ini dan Lampiran H, kita akan melihat berbagai teknik perangkat keras dan perangkat lunak yang memungkinkan kita untuk memanfaatkan paralelisme tingkat instruksi untuk memanfaatkan sepenuhnya potensi unit fungsional dalam prosesor. Kunci sebagian besar teknik ini adalah mengetahui kapan dan bagaimana pemesanan di antara instruksi dapat diubah. Dalam contoh, kita membuat banyak perubahan seperti itu, yang bagi kita, sebagai manusia, jelas diijinkan. Dalam
prakteknya, proses ini harus dilakukan secara metodis baik oleh kompilator atau oleh perangkat keras. Untuk mendapatkan kode akhir unrolled, kami harus membuat
keputusan dan transformasi berikut:
• Tentukan bahwa unrolling loop akan berguna dengan menemukan bahwa
iterasi loop bersifat independen, kecuali untuk kode pemeliharaan loop.
• Gunakan register yang berbeda untuk menghindari kendala yang tidak
perlu yang akan dipaksa dengan menggunakan register yang sama untuk perhitungan yang berbeda (mis., Ketergantungan nama).
• Hilangkan tes ekstra dan instruksi cabang dan sesuaikan penghenti loop
dan kode iterasi.
• Tentukan bahwa loads dan penyimpanan di loop yang tidak tergulung
dapat dipertukarkan dengan mengamati bahwa loads dan penyimpanan dari iterasi yang berbeda bersifat independen. Transformasi ini memerlukan analisis alamat memori dan
menemukan bahwa mereka tidak mengacu ke alamat yang sama.
• Jadwalkan kode, lestarikan ketergantungan yang diperlukan untuk
menghasilkan hasil yang sama seperti kode asli.
Persyaratan utama yang mendasari semua transformasi ini adalah pemahaman tentang bagaimana satu instruksi bergantung pada yang lain dan bagaimana instruksinya dapat diubah atau disusun ulang mengingat ketergantungannya.
Tiga efek yang berbeda membatasi keuntungan dari loop unrolling: (1) penurunan jumlah overhead yang diamortisasi dengan masing-masing gulungan, (2) batasan ukuran kode, dan (3) keterbatasan kompilator. Mari kita pertimbangkan pertanyaan tentang overhead loop terlebih dahulu. Ketika kita membuka gulungan loop
empat kali, itu menghasilkan paralelisme yang cukup di antara instruksi bahwa loop bisa dijadwalkan tanpa siklus stalls. Sebenarnya, dalam 14 siklus clock, hanya 2 siklus yaitu loop overhead: DADDUI, yang mempertahankan nilai indeks, dan BNE, yang mengakhiri loop. Jika loop dibuka dua kali lipat, overhead dikurangi dari 1/2 siklus per iterasi asli menjadi 1/4.
Batas kedua untuk unrolling adalah pertumbuhan dalam ukuran kode yang dihasilkan. Untuk loop yang lebih besar, ukuran kode mungkin menjadi perhatian terutama jika menyebabkan peningkatan tingkat kehilangan cache instruksi.
Faktor lain yang sering kali lebih penting daripada ukuran kode adalah kekurangan potensial pada register yang dibuat dengan unrolling dan scheduling yang agresif. Efek sekunder yang dihasilkan dari penjadwalan instruksi pada segmen kode besar disebut regresi register. Timbul karena kode penjadwalan untuk meningkatkan ILP menyebabkan jumlah nilai hidup meningkat. Setelah penjadwalan instruksi yang agresif, mungkin tidak memungkinkan untuk mengalokasikan semua nilai live ke register. Kode yang berubah, sementara secara teoritis lebih cepat, mungkin kehilangan beberapa atau semua keuntungannya karena menghasilkan kekurangan register. Tanpa membuka gulungan, penjadwalan agresif cukup dibatasi oleh cabang sehingga tekanan register jarang menjadi masalah. Kombinasi penjadwalan ulang dan penjadwalan dapat menyebabkan masalah ini. Masalahnya menjadi sangat menantang dalam prosesor multi masalah yang memerlukan pemaparan urutan instruksi yang lebih independen yang eksekusi dapat tumpang tindih. Secara umum, penggunaan transformasi tingkat tinggi yang canggih, yang perbaikan potensialnya sulit diukur sebelum pembuatan kode terperinci, telah menyebabkan peningkatan kompleksitas kompiler modern yang signifikan.
Loop unrolling adalah metode sederhana namun berguna untuk meningkatkan ukuran fragmen kode garis lurus yang dapat dijadwalkan secara efektif. Transformasi ini berguna dalam berbagai prosesor, mulai dari jaringan pipeline sederhana seperti yang telah kita pelajari sejauh ini dengan superscalars multipel dan VLIW yang dieksplorasi nanti di bab ini.
Mengurangi Biaya Cabang dengan Prediksi Cabang Lanjutan
Karena kebutuhan untuk menerapkan ketergantungan kontrol melalui bahaya cabang dan stalls, cabang akan merusak kinerja jaringan pipeline. Loop unrolling adalah salah satu cara untuk mengurangi jumlah bahaya cabang; Kita juga bisa mengurangi kerugian kinerja cabang dengan meramalkan bagaimana mereka akan berperilaku. Pada Lampiran C, kami memeriksa prediktor cabang sederhana yang mengandalkan informasi kompilasi atau pada perilaku dinamis teramati dari cabang yang diisolasi. Karena jumlah instruksi dalam penerbangan telah meningkat, semakin penting prediksi
cabang yang lebih akurat. Pada bagian ini, kami memeriksa teknik untuk meningkatkan akurasi prediksi dinamis.
Prediktor Cabang Korelasi
Skema prediktor 2-bit hanya menggunakan perilaku terkini cabang tunggal untuk memprediksi perilaku masa depan cabang tersebut. Ada kemungkinan untuk memperbaiki akurasi prediksi jika kita juga melihat perilaku cabang baru belakangan ini, bukan hanya cabang yang ingin kita prediksi. Pertimbangkan fragmen kode kecil dari patokan eqntott, anggota suite benchmark SPEC awal yang menunjukkan perilaku prediksi cabang yang sangat buruk:
if (aa==2) aa=0; if (bb==2) bb=0;
if (aa!=bb) {
Berikut adalah kode MIPS yang biasanya kami buat untuk fragmen kode ini dengan asumsi bahwa aa dan bb ditugaskan untuk register R1 dan R2:
DADDIU R3,R1,# – 2
BNEZ R3,L1 ;branch b1 (aa!=2)
DADD R1,R0,R0 ;aa=0 L1: DADDIU R3,R2,# – 2 BNEZ R3,L2 ;branch b2 (bb!=2) DADD R2,R0,R0 ;bb=0 L2: DSUBU R3,R1,R2 ;R3=aa-bb
BEQZ R3,L3 ;branch b3 (aa==bb)
Mari kita label cabang-cabang ini b1, b2, dan b3. Pengamatan kunci adalah bahwa perilaku cabang b3 berkorelasi dengan perilaku cabang b1 dan b2. Jelas, jika cabang b1 dan b2 sama-sama tidak diambil (yaitu, jika kondisinya baik dievaluasi ke true dan aa dan bb keduanya ditugaskan 0), maka b3 akan diambil, karena aa dan bb
jelas sama. Prediktor yang hanya menggunakan perilaku cabang tunggal untuk memprediksi hasil cabang itu tidak akan pernah bisa menangkap perilaku ini.
Prediktor cabang yang menggunakan tingkah laku cabang lainnya membuat prediksi disebut correlating predictors atau two-level predictors. Prediktor berkorelasi
yang ada menambahkan informasi tentang perilaku cabang terbaru untuk menentukan bagaimana memprediksi cabang yang diberikan. Sebagai contoh, prediktor (1,2) menggunakan perilaku cabang terakhir untuk memilih dari antara sepasang prediktor cabang 2-bit dalam memprediksi cabang tertentu. Dalam kasus umum, prediktor (m, n) menggunakan perilaku cabang m terakhir untuk memilih dari prediktor cabang 2m, yang masing-masing merupakan prediktor n-bit untuk cabang tunggal. Daya tarik jenis peramalan cabang yang berkorelasi ini adalah bahwa ia dapat menghasilkan tingkat prediksi yang lebih tinggi daripada skema 2-bit dan hanya membutuhkan sejumlah perangkat keras tambahan sepele.
Kesederhanaan perangkat keras berasal dari pengamatan sederhana: Sejarah global dari cabang m terbaru dapat dicatat dalam register geser m-bit, di mana masing-masing bit mencatat apakah cabang diambil atau tidak diambil. Penyangga prediksi cabang kemudian dapat diindeks dengan menggunakan rangkaian bit loworder dari alamat cabang dengan sejarah global m-bit. Sebagai contoh, pada buffer (2,2) dengan total 64 entri, 4 bit alamat low-order dari cabang (word address) dan 2 bit global yang mewakili perilaku dari dua cabang yang terakhir dieksekusi membentuk 6 bit Index yang bisa digunakan untuk mengindeks 64 counter.
Seberapa jauhkah detektor cabang yang bekerja berkorelasi bila dibandingkan dengan skema 2-bit standar? Untuk membandingkannya dengan cukup, kita harus membandingkan prediktor yang menggunakan jumlah bit negara yang sama. Jumlah bit dalam prediktor (m, n) adalah
2m × n × Jumlah entri prediksi yang dipilih oleh alamat cabang Prediktor 2-bit tanpa sejarah global hanyalah prediktor (0,2).
Contoh Berapa banyak bit yang ada pada prediktor cabang (0,2) dengan entri 4K? Berapa banyak entri yang berada dalam prediktor (2.2) dengan jumlah bit yang sama?
Jawab Prediktor dengan entri 4K memiliki
20 × 2 × 4K = 8K bits
Berapa banyak entri cabang terpilih berada pada prediktor (2.2) yang memiliki total 8K bit dalam buffer prediksi? Kita tahu itu
22 × 2 × Jumlah entri prediksi yang dipilih oleh cabang = 8K Oleh karena itu, jumlah entri prediksi dipilih oleh cabang = 1K.
Gambar 3.3 membandingkan tingkat kesalahpahaman prediktor sebelumnya (0,2) dengan entri 4K dan prediktor (2,2) dengan entri 1K. Seperti yang dapat Anda lihat, prediktor yang berkorelasi ini tidak hanya mengungguli prediktor 2-bit sederhana dengan jumlah bit negara yang sama, namun juga sering melampaui prediktor 2-bit dengan jumlah entri yang tidak terbatas.
Prediktor Turnamen: Menggabungkan Prediktor Lokal dan Global secara Adaptif
Motivasi utama untuk menghubungkan prediktor cabang berasal dari pengamatan adalah prediktor 2-bit standar yang hanya menggunakan informasi lokal gagal pada beberapa cabang penting dan, dengan menambahkan informasi global, kinerjanya dapat ditingkatkan. Prediktor turnamen mengambil wawasan ini ke tingkat berikutnya, dengan menggunakan beberapa prediktor, biasanya satu berdasarkan informasi global dan satu berdasarkan informasi lokal, dan menggabungkann ya dengan pemilih. Prediktor turnamen dapat mencapai akurasi yang lebih baik dengan ukuran sedang (8K-32K bit) dan juga menggunakan jumlah bit prediksi yang sangat besar secara efektif. Prediktor turnamen yang ada menggunakan penghitung jenuh 2-bit per cabang untuk memilih di antara dua prediktor yang berbeda berdasarkan prediktor (lokal, global, atau bahkan beberapa campuran) yang paling efektif dalam prediksi baru- baru ini. Seperti pada prediktor 2-bit sederhana, penghitung jenuh memerlukan dua kesalahan prediksi sebelum mengubah identitas prediktor pilihan. Keuntungan dari prediktor turnamen adalah kemampuannya untuk memilih prediktor yang tepat untuk cabang tertentu, yang sangat penting untuk tolok ukur integer. Prediktor turnamen yang khas akan memilih prediktor global hampir 40% dari waktu untuk benchmark integer SPEC dan kurang dari 15% waktu untuk benchmark SPEC FP. Selain prosesor Alpha
yang mempelopori prediksi turnamen, prosesor AMD terbaru, termasuk Opteron dan Phenom, telah menggunakan prediktor bergaya turnamen.
Gambar 3.3 Perbandingan prediktor 2 bit. Prediktor yang tidak berkorelasi untuk 4096 bit pertama, diikuti oleh prediktor 2-bit yang tidak berkorelasi dengan entri tak terbatas dan prediktor 2 bit dengan 2 bit riwayat global dan total 1024 entri. Meskipun data ini untuk versi SPEC yang lebih lama, data untuk benchmark SPEC yang lebih baru akan menunjukkan perbedaan akurasi yang sama.
Jumlah bit menggunakan SPEC89 sebagai benchmark. Seperti yang kita lihat sebelumnya, kemampuan prediksi prediktor lokal tidak membaik melebihi ukuran tertentu. Prediktor yang berkorelasi menunjukkan peningkatan yang signifikan, dan prediksi turnamen menghasilkan kinerja yang sedikit lebih baik. Untuk versi SPEC
yang lebih baru, hasilnya akan serupa, namun perilaku asimtotik tidak akan tercapai sampai ukuran prediktor sedikit lebih besar. Prediktor lokal terdiri dari prediktor dua level. Tingkat atas adalah tabel sejarah lokal yang terdiri dari 1024 entri 10-bit; Setiap entri 10 bit sesuai dengan 10 hasil cabang terbaru untuk entri. Artinya, jika cabang diambil 10 kali atau lebih berturut-turut, entri dalam tabel sejarah lokal akan menjadi 1s. Jika cabang diambil dan diambil secara bergantian, entri sejarah terdiri dari alternating 0s dan 1s. Sejarah 10-bit ini memungkinkan pola hingga 10 cabang ditemukan dan diprediksi. Entri yang dipilih dari tabel sejarah lokal digunakan untuk mengindeks tabel entri 1K yang terdiri dari penghitung saturasi 3 bit, yang memberikan prediksi lokal. Kombinasi ini, yang menggunakan total 29K bit, menyebabkan akurasi
yang tinggi dalam prediksi cabang.
Gambar 3.4 Tingkat salah prediksi untuk tiga prediktor yang berbeda pada SPEC89 karena jumlah bit meningkat. Prediktor adalah prediktor 2-bit lokal, prediktor korelasi yang terstruktur secara optimal dalam penggunaan informasi global dan lokal pada setiap titik dalam grafik, dan prediksi turnamen. Meskipun data ini untuk versi SPEC yang lebih lama, data untuk benchmark SPEC yang lebih baru akan menunjukkan perilaku serupa, mungkin konvergen ke batas asimtotik pada ukuran prediktor yang
sedikit lebih besar.
Intel telah merilis hanya sejumlah informasi terbatas tentang prediksi cabang Core i7, yang didasarkan pada prediktor sebelumnya yang digunakan pada chip Core Duo. I7 menggunakan prediktor dua tingkat yang memiliki prediktor tingkat pertama yang lebih kecil, yang dirancang untuk memenuhi batasan siklus memprediksi cabang setiap siklus clock, dan prediktor tingkat kedua yang lebih besar sebagai cadangan. Setiap prediktor menggabungkan tiga prediktor yang berbeda: (1) prediktor dua bit sederhana, yang diperkenalkan pada Lampiran C (dan digunakan dalam prediksi turnamen yang dibahas di atas); (2) prediktor sejarah global, seperti yang baru saja kita lihat; Dan (3) prediksi keluar loop. Prediktor loop keluar menggunakan counter untuk memprediksi jumlah cabang yang diambil (yang merupakan jumlah loop pengulangan) untuk cabang yang terdeteksi sebagai cabang lingkaran. Untuk setiap cabang, prediksi terbaik dipilih dari tiga prediktor dengan melacak keakuratan setiap prediksi, seperti prediksi turnamen. Selain prediktor utama bertingkat ini, unit terpisah memprediksi alamat target untuk cabang tidak langsung, dan tumpukan untuk memprediksi alamat pengirim juga digunakan.
Gambar 3.5 Tingkat kesalahpahaman untuk 19 dari benchmark SPECCPU2006 versus jumlah cabang yang berhasil dipekerjakan sedikit lebih tinggi rata-rata untuk tolok ukur integer daripada FP (4% versus 3%). Lebih penting lagi, ini jauh lebih tinggi untuk beberapa tolok ukur.
Seperti dalam kasus lain, spekulasi menyebabkan beberapa tantangan dalam mengevaluasi prediktor, karena cabang yang salah diketahui dapat dengan mudah mengarah ke cabang lain yang diambil dan salah paham. Untuk menjaga hal-hal sederhana, kita melihat jumlah mispredictions sebagai persentase dari jumlah cabang yang berhasil diselesaikan (yang bukan hasil dari misspeculation). Gambar 3.5 menunjukkan data ini untuk 19 benchmark SPECCPU 2006. Tolok ukur ini jauh lebih besar dari SPEC89 atau SPEC2000, dengan akibatnya tingkat kesalahpahaman sedikit lebih tinggi daripada pada Gambar 3.4 bahkan dengan kombinasi prediktor yang lebih rumit. Karena misprediksi cabang mengarah pada spekulasi yang tidak efektif, hal itu berkontribusi pada terbuangnya pekerjaan, seperti yang akan kita lihat nanti di bab ini.
Mengatasi Bahaya Data dengan Penjadwalan Dinamis
Sebuah pipeline statis yang dijadwalkan secara statistik mengambil sebuah instruksi dan mengeluarkannya, kecuali ada ketergantungan data antara instruksi yang sudah ada dalam pipeline dan instruksi yang tidak dapat disembunyikan dengan melewati atau meneruskan. (Forwarding logic mengurangi latensi pipelin e yang efektif sehingga ketergantungan tertentu tidak mengakibatkan bahaya.) Jika ada ketergantungan data yang tidak dapat disembunyikan, maka perangkat keras deteksi bahaya memadamkan jaringan pipeline yang dimulai dengan instruksi yang menggunakan hasilnya. Tidak ada instruksi baru yang diambil atau dikeluarkan sampai ketergantungan tersebut dihapus.
Pada bagian ini, kita mengeksplorasi penjadwalan dinamis, di mana perangkat keras mengatur ulang eksekusi instruksi untuk mengurangi stalls sambil mempertahankan aliran data dan perilaku pengecualian. Penjadwalan dinamis menawarkan beberapa keuntungan. Pertama, ini memungkinkan kode yang disusun dengan satu pipeline agar berjalan efisien pada jalur pipeline yang berbeda, sehingga menghilangkan kebutuhan untuk memiliki banyak binari dan mengkompilasi ulang untuk mikroarsitektur yang berbeda. Di lingkungan komputasi saat ini, di mana sebagian besar perangkat lunak berasal dari pihak ketiga dan didistribusikan dalam bentuk biner, keuntungan ini signifikan. Kedua, memungkinkan penanganan beberapa
kasus bila ketergantungan tidak diketahui pada waktu kompilasi; Misalnya, mereka mungkin melibatkan referensi memori atau cabang yang bergantung pada data, atau mungkin dihasilkan dari lingkungan pemrograman modern yang menggunakan tautan dinamis atau pengiriman. Ketiga, dan mungkin yang terpenting, ini memungkinkan prosesor untuk mentolerir penundaan yang tidak dapat diprediksi, seperti cache misses, dengan mengeksekusi kode lainnya sambil menunggu ketinggalan untuk menyelesaikannya. Pada Bagian 3.6, kami mengeksplorasi spekulasi perangkat keras, teknik dengan keuntungan kinerja tambahan, yang dibangun berdasarkan penjadwalan dinamis. Seperti yang akan kita lihat, keuntungan penjadwalan dinamis diperoleh dengan biaya peningkatan kompleksitas perangkat keras yang signifikan.
Meskipun prosesor yang dijadwalkan secara dinamis tidak dapat mengubah arus data, ia mencoba untuk menghindari mengulur-ulur saat ada ketergantungan. Sebaliknya, penjadwalan pipeline statis oleh kompilator (dibahas di Bagian 3.2) mencoba untuk meminimalkan stalls dengan memisahkan instruksi yang dependen sehingga tidak menimbulkan bahaya. Tentu saja, penjadwalan pipeline kompilator juga dapat digunakan pada kode yang ditujukan untuk berjalan pada prosesor dengan pipeline yang dijadwalkan secara dinamis.
Penjadwalan Dinamis: Ide
Keterbatasan utama teknik pipelining sederhana adalah bahwa mereka menggunakan instruksi-instruksi dan eksekusi dalam urutan: Instruksi dikeluarkan dalam urutan program, dan jika instruksi dihentikan dalam pipeline, instruksi selanjutnya tidak dapat dilanjutkan. Jadi, jika ada ketergantungan antara dua instruksi jarak dekat dalam pipeline, ini akan menyebabkan bahaya dan stalls akan berakibat. Jika ada beberapa unit fungsional, unit ini bisa menganggur. Jika instruksi j bergantung pada instruksi yang berjalan lama, saat ini dalam eksekusi di dalam pipeline, maka semua instruksi setelah j harus terhenti sampai selesai dan j dapat dijalankan. Misaln ya, perhatikan kode ini:
DIV.D F0,F2,F4
ADD.D F10,F0,F8
SUB.D F12,F8,F14
Instruksi SUB.D tidak dapat dijalankan karena ketergantungan ADD.D pada DIV.D menyebabkan jaringan pipeline berhenti; Namun, SUB.D bukan data tergantu ng pada apapun dalam pipeline. Bahaya ini menciptakan batasan kinerja yang dapat dieliminasi dengan tidak memerlukan instruksi untuk dieksekusi dalam urutan program. Dalam pipeline lima tahap klasik, bahaya struktural dan data dapat diperiksa saat instruksi decode (ID): Bila instruksi dapat dijalankan tanpa bahaya, dikeluarkan dari ID karena mengetahui bahwa semua bahaya data telah teratasi.
Agar kita dapat mengeksekusi SUBD dalam contoh di atas, kita harus memisahkan proses penerbitan menjadi dua bagian: memeriksa bahaya struktural dan menunggu tidak adanya bahaya data. Jadi, kita masih menggunakan in -order instruction issue (yaitu instruksi yang dikeluarkan dalam urutan program), namun kami menginginkan sebuah instruksi untuk memulai eksekusi segera setelah operan datanya tersedia. Pipeline semacam itu melakukan eksekusi out-of-order, yang menyiratkan penyelesaian out-of-order.
Eksekusi di luar kebiasaan mengenalkan kemungkinan bahaya WAR dan WAW, yang tidak ada dalam pipeline integer lima tahap dan perpanjangan logisnya ke pipeline floating-point dalam pesanan. Perhatikan urutan kode floating-point MIPS berikut ini:
DIV.D F0,F2,F4
ADD.D F6,F0,F8
SUB.D F8,F10,F14
MUL.D F6,F10,F8
Ada antidependensi antara ADD.D dan SUB.D, dan jika pipeline mengeksekusi SUBD sebelum ADD.D (yang menunggu DIV.D), maka akan melanggar antidependensi, menghasilkan bahaya WAR . Demikian juga, untuk menghindari penyalahgunaan keluaran, seperti penulisan F6 oleh MUL.D, bahaya WAW harus ditangani. Seperti yang akan kita lihat, kedua bahaya ini dihindari dengan penggunaan daftar nama pengganti.
Penyelesaian out-of-order juga menciptakan komplikasi utama dalam penanganan pengecualian. Penjadwalan dinamis dengan penyelesaian out-of-order harus menjaga perilaku pengecualian dalam arti bahwa persis pengecualian yang akan muncul jika program dijalankan dalam urutan program yang ketat benar -benar muncul. Prosesor yang dijadwalkan secara dinamis mempertahankan perilaku pengecualian dengan menunda pemberitahuan pengecualian terkait sampai prosesor mengetahui bahwa instruksinya seharusnya selesai berikutnya.
Meskipun perilaku pengecualian harus dipertahankan, prosesor yang dijadwalkan secara dinamis dapat menghasilkan pengecualian yang tidak tepat. Pengecualian tidak tepat jika status prosesor bila pengecualian dinaikkan tidak terlihat
persis seperti jika instruksi dijalankan secara berurutan dalam urutan program yang ketat. Pengecualian yang tidak jelas dapat terjadi karena dua kemungkinan:
1. Pipeline mungkin telah menyelesaikan instruksi yang kemudian dalam urutan program daripada instruksi yang menyebabkan pengecualian.
2. Pipeline mungkin belum menyelesaikan beberapa instruksi yang lebih awal dalam urutan program daripada instruksi yang menyebabkan pengecualian.
Pengecualian yang tidak tepat membuat sulit untuk memulai ulang eksekusi setelah pengecualian. Daripada mengatasi masalah tersebut di bagian ini, kita akan membahas sebuah solusi yang memberikan pengecualian yang tepat dalam konteks prosesor dengan spekulasi di Bagian 3.6. Untuk pengecualian floating-point, solusi lain
telah digunakan, seperti yang dibahas pada Lampiran J.
Untuk mengijinkan eksekusi out-of-order, pada dasarnya kita membagi tahap pipeline ID dari pipeline lima tingkat sederhana menjadi dua tahap:
1. Instruksi Decode-Decode, periksa bahaya structural.
2. Baca operan-Tunggu hingga tidak ada bahaya data, baca operan.
Tahap pengambilan instruksi mendahului tahap penerbitan dan dapat diambil baik ke dalam register instruksi atau ke dalam antrian instruksi yang tertunda; Instruksi kemudian dikeluarkan dari register atau antrian. Tahap eksekusi mengikuti tahap operan baca, sama seperti pada pipeline lima tahap. Eksekusi mungkin memakan banyak siklus,
tergantung pada operasi.
Kita membedakan kapan sebuah instruksi dimulai eksekusi dan kapan ia menyelesaikan eksekusi; Antara dua kali, instruksi sedang dalam eksekusi. Pipeline kami memungkinkan banyak instruksi untuk dieksekusi pada saat bersamaan; Tanpa kemampuan ini, keuntungan utama penjadwalan dinamis pun hilang. Memiliki banyak instruksi dalam eksekusi sekaligus membutuhkan beberapa unit fungsional, unit fungsional pipeline, atau keduanya. Karena dua kemampuan ini - unit fungsional pipelined dan beberapa unit fungsional - pada dasarnya setara untuk tujuan kontrol pipeline, kita akan menganggap prosesor memiliki beberapa unit fungsional.
Dalam pipeline yang dijadwalkan secara dinamis, semua instruksi melewati tahap masalah secara berurutan (in-order issue); Namun, mereka dapat terhenti atau melewati satu sama lain di tahap kedua (read operands) dan dengan demikian masuk
eksekusi tidak sesuai urutan. Scoreboarding adalah teknik untuk mengizinkan instruksi agar tidak rusak bila ada sumber daya yang memadai dan tidak ada ketergantungan data; Ini dinamai CDC 6600 scoreboard, yang mengembangkan kemampuan ini. Di sini, kita fokus pada teknik yang lebih canggih, yang disebut algoritma Tomasulo. Perbedaan utama adalah bahwa algoritma Tomasulo menangani ketergantungan antidependensi dan keluaran dengan mengubah secara efektif register secara dinamis. Selain itu, algoritma Tomasulo dapat diperluas untuk menangani spekulasi, teknik untuk mengurangi efek ketergantungan kontrol dengan memprediksi hasil cabang, melaksanakan instruksi di alamat tujuan yang diprediksi, dan mengambil tindakan perbaikan saat prediksi salah. Sementara penggunaan scoreboarding mungkin cukup untuk mendukung superscalar dua masalah sederhana seperti ARM A8, prosesor yang lebih agresif, seperti Intel I7 empat isu, mendapat keuntungan dari penggunaan eksekusi di luar pesanan.
Penjadwalan Dinamis Menggunakan Pendekatan Tomasulo
Unit floating-point IBM 360/91 menggunakan skema yang canggih untuk memungkinkan eksekusi out-of-order. Skema ini, yang ditemukan oleh Robert Tomasulo, dilacak saat operan mendapat instruksi tersedia untuk memini malkan bahaya RAW dan mengenalkan daftar nama di perangkat keras untuk meminimalkan bahaya WAW dan WAR. Ada banyak variasi pada skema ini dalam prosesor modern, walaupun konsep kunci untuk melacak ketergantungan instruksi memungkinkan eksekusi segera setelah operan tersedia dan mengganti nama register untuk menghindari bahaya WAR dan WAW adalah karakteristik umum.
Tujuan IBM adalah untuk mencapai kinerja floating-point yang tinggi dari satu set instruksi dan dari kompiler yang dirancang untuk keseluruhan 360 keluarga komputer, bukan dari kompiler khusus untuk prosesor high-end. Arsitektur 360 hanya memiliki empat register floating-point presisi ganda, yang membatasi keefektifan penjadwalan kompilator; Fakta ini merupakan motivasi lain untuk pendekatan Tomasulo. Selain itu, IBM 360/91 memiliki akses memori yang panjang dan penundaan floating-point yang panjang, yang algoritma Tomasulo dirancang untuk mengatasi.
Pada akhir bagian, kita akan melihat bahwa algoritma Tomasulo juga dapat mendukung eksekusi berulang dari beberapa iterasi satu lingkaran.
Kami menjelaskan algoritma, yang berfokus pada unit floating-point dan unit loadstore, dalam konteks set instruksi MIPS. Perbedaan utama antara MIPS dan 360 adalah adanya instruksi register-memory pada arsitektur yang terakhir. Karena algoritma Tomasulo menggunakan unit fungsional loads, tidak ada perubahan signifikan yang diperlukan untuk menambahkan mode pengalamatan memori register. IBM 360/91 juga memiliki unit fungsional pipeline, bukan beberapa unit fungsional, namun kami menggambarkan algoritma tersebut seolah-olah ada beberapa unit fungsional. Ini adalah perpanjangan konseptual sederhana untuk j uga menyalurkan unit fungsional tersebut.
Seperti yang akan kita lihat, bahaya RAW dihindari dengan mengeksekusi instruksi hanya jika operannya tersedia, itulah yang disediakan oleh pendekatan scoreboarding sederhana. Bahaya WAR dan WAW, yang timbul dari ketergantungan nama, dihilangkan dengan register renaming. Register renaming menghilangkan bahaya ini dengan mengganti nama semua register tujuan, termasuk yang membaca atau menulis yang tertunda untuk instruksi sebelumnya, sehingga penulisan di luar pesanan tidak mempengaruhi instruksi yang bergantung pada nilai operan sebelumnya.
Untuk lebih memahami bagaimana mengganti nama register menghilangkan bahaya WAR dan WAW, pertimbangkan urutan kode contoh berikut yang mencakup potensi bahaya WAR dan WAW:
DIV.D F0,F2,F4
ADD.D F6,F0,F8
S.D F6,0(R1)
SUB.D F8,F10,F14
MUL.D F6,F10,F8
Ada dua antidependensi: antara ADD.D dan SUB.D dan antara S.D dan MUL.D. Ada juga ketergantungan output antara ADD.D dan MUL.D, yang menyebabkan tiga bahaya yang mungkin terjadi: Bahaya pada penggunaan F8 oleh ADD.D dan penggunaan F6 oleh SUBD, dan juga WAW Bahaya sejak ADD.D bisa selesai lebih
dari MUL.D. Ada juga tiga ketergantungan data sejati: antara DIV.D dan ADD.D, antara SUB.D dan MUL.D, dan antara ADD.D dan S.D.
Ketergantungan nama ketiga ini bisa dihilangkan dengan register renaming. Untuk lebih mudahnya, asumsikan adanya dua register sementara, S dan T. Dengan menggunakan S dan T, urutannya dapat ditulis ulang tanpa ketergantungan seperti:
DIV.D F0,F2,F4
ADD.D S,F0,F8
S.D S,0(R1)
SUB.D T,F10,F14
MUL.D F6,F10,T
Selain itu, penggunaan selanjutnya F8 harus diganti oleh register T. Pada segmen kode ini, proses renaming dapat dilakukan secara statis oleh kompilator. Menemukan penggunaan F8 yang nantinya ada dalam kode memerlukan analisis kompiler atau
dukungan perangkat keras yang canggih, karena mungkin ada intervensi antara segmen kode di atas dan penggunaan F8 selanjutnya. Seperti yang akan kita lihat, algoritma Tomasulo dapat menangani renaming di cabang-cabang.
Dalam skema Tomasulo, register renaming disediakan oleh reservation station, yang menyangga operan instruksi yang menunggu untuk dikeluarkan. Ide dasarnya adalah bahwa sebuah reservation station menjemput dan menyangga sebuah operan segera setelah tersedia, sehingga tidak perlu mendapatkan operan dari daftar. Selain itu, instruksi yang tertunda menunjuk reservation station yang akan memberi masukan mereka. Akhirnya, ketika berturut-turut menulis ke register tumpang tindih dalam eksekusi, hanya yang terakhir yang benar-benar digunakan untuk mengupdate register. Saat instruksi dikeluarkan, penspesifikasi register untuk operan yang tertunda diganti namanya menjadi nama reservation station, yang menyediakan renaming register.
Ketika terdapat lebih banyak reservation station daripada register nyata, teknik ini bahkan dapat menghilangkan bahaya yang timbul dari ketergantungan nama yang tidak dapat dihilangkan oleh kompilator. Saat kita mengeksplorasi komponen skema Tomasulo, kita akan kembali ke topik penggantian nama dan melihat secara tepat bagaimana penggantian nama terjadi dan bagaimana menghilangkan bahaya WAR dan