Jika register sangat cepat, mengapa kita tidak memiliki lebih banyak?

88

Dalam 32bit, kami memiliki 8 register "tujuan umum". Dengan 64bit, jumlahnya menjadi dua kali lipat, tetapi tampaknya terlepas dari perubahan 64bit itu sendiri.
Sekarang, jika register begitu cepat (tidak ada akses memori), mengapa tidak ada lebih banyak dari mereka secara alami? Bukankah pembuat CPU harus bekerja dengan register sebanyak mungkin ke dalam CPU? Apa batasan logis mengapa kita hanya memiliki jumlah yang kita miliki?

Xeo
sumber
CPU dan GPU menyembunyikan latensi terutama oleh cache dan multithreading masif. Jadi, CPU memiliki (atau membutuhkan) sedikit register, sedangkan GPU memiliki puluhan ribu register. Lihat makalah survei saya pada file register GPU yang membahas semua trade-off dan faktor ini.
pengguna984260

Jawaban:

119

Ada banyak alasan mengapa Anda tidak hanya memiliki register dalam jumlah besar:

  • Mereka sangat terkait dengan sebagian besar tahapan pipeline. Sebagai permulaan, Anda perlu melacak masa hidup mereka, dan meneruskan hasil kembali ke tahap sebelumnya. Kompleksitas menjadi sulit diselesaikan dengan sangat cepat, dan jumlah kabel (secara harfiah) yang terlibat tumbuh dengan kecepatan yang sama. Ini mahal di area, yang pada akhirnya berarti mahal pada daya, harga, dan kinerja setelah titik tertentu.
  • Ini membutuhkan ruang pengkodean instruksi. 16 register membutuhkan 4 bit untuk sumber dan tujuan, dan 4 bit lainnya jika Anda memiliki instruksi 3-operan (misalnya ARM). Itu banyak sekali ruang pengkodean set instruksi yang digunakan hanya untuk menentukan register. Ini pada akhirnya berdampak pada decoding, ukuran kode, dan lagi kompleksitas.
  • Ada cara yang lebih baik untuk mencapai hasil yang sama ...

Hari-hari ini kami benar-benar memiliki banyak register - mereka tidak diprogram secara eksplisit. Kami memiliki "register renaming". Meskipun Anda hanya mengakses set kecil (8-32 register), sebenarnya mereka didukung oleh set yang jauh lebih besar (misalnya 64-256). CPU kemudian melacak visibilitas setiap register, dan mengalokasikannya ke set yang diubah namanya. Misalnya, Anda dapat memuat, memodifikasi, lalu menyimpan ke register berkali-kali berturut-turut, dan masing-masing operasi ini benar-benar dilakukan secara independen bergantung pada cache yang terlewat, dll. Di ARM:

ldr r0, [r4]
add r0, r0, #1
str r0, [r4]
ldr r0, [r5]
add r0, r0, #1
str r0, [r5]

Core Cortex A9 melakukan penggantian nama register, jadi pemuatan pertama ke "r0" sebenarnya masuk ke register virtual yang diubah namanya - sebut saja "v0". Pemuatan, penambahan, dan penyimpanan terjadi pada "v0". Sementara itu, kami juga melakukan pemuatan / modifikasi / penyimpanan ke r0 lagi, tetapi itu akan diganti namanya menjadi "v1" karena ini adalah urutan yang sepenuhnya independen menggunakan r0. Katakanlah beban dari pointer di "r4" terhenti karena cache tidak ditemukan. Tidak apa-apa - kita tidak perlu menunggu "r0" siap. Karena namanya diganti, kita dapat menjalankan urutan berikutnya dengan "v1" (juga dipetakan ke r0) - dan mungkin itu adalah cache hit dan kita baru saja meraih kemenangan kinerja yang besar.

ldr v0, [v2]
add v0, v0, #1
str v0, [v2]
ldr v1, [v3]
add v1, v1, #1
str v1, [v3]

Saya pikir x86 adalah hingga sejumlah besar register berganti nama hari ini (rata-rata 256). Itu berarti memiliki 8 bit dikalikan 2 untuk setiap instruksi hanya untuk mengatakan apa sumber dan tujuannya. Ini akan secara besar-besaran meningkatkan jumlah kabel yang dibutuhkan di seluruh inti, dan ukurannya. Jadi ada sweet spot sekitar 16-32 register yang telah ditetapkan sebagian besar desainer, dan untuk desain CPU yang rusak, penggantian nama register adalah cara untuk menguranginya.

Sunting : Pentingnya eksekusi out-of-order dan register penggantian nama ini. Begitu Anda memiliki OOO, jumlah register tidak terlalu menjadi masalah, karena itu hanyalah "tag sementara" dan diganti namanya menjadi set register virtual yang jauh lebih besar. Anda tidak ingin angkanya terlalu kecil, karena sulit untuk menulis urutan kode yang kecil. Ini adalah masalah untuk x86-32, karena 8 register yang terbatas berarti banyak sementara yang berakhir melalui tumpukan, dan inti membutuhkan logika ekstra untuk meneruskan baca / tulis ke memori. Jika Anda tidak memiliki OOO, Anda biasanya berbicara tentang inti kecil, dalam hal ini kumpulan register besar adalah manfaat biaya / kinerja yang buruk.

Jadi ada sweet spot alami untuk ukuran bank register yang maksimal sekitar 32 register yang dirancang untuk sebagian besar kelas CPU. x86-32 memiliki 8 register dan pastinya terlalu kecil. ARM menggunakan 16 register dan ini adalah kompromi yang bagus. 32 register sedikit terlalu banyak jika ada - Anda akhirnya tidak membutuhkan 10 atau lebih yang terakhir.

Tak satu pun dari sentuhan ini pada register tambahan yang Anda dapatkan untuk SSE dan koprosesor titik mengambang vektor lainnya. Itu masuk akal sebagai set tambahan karena berjalan secara independen dari inti integer, dan tidak menumbuhkan kompleksitas CPU secara eksponensial.

John Ripley
sumber
12
Jawaban luar biasa - Saya ingin memasukkan alasan lain ke dalam campuran - semakin banyak register yang dimiliki, semakin banyak waktu yang dibutuhkan untuk melemparkannya ke / menariknya dari tumpukan saat konteks beralih. Jelas bukan masalah utama, tapi pertimbangan.
Akankah
7
@Apa poin yang bagus. Namun, arsitektur dengan banyak register memiliki cara untuk mengurangi biaya ini. ABI biasanya memiliki callee-save dari sebagian besar register, jadi Anda hanya perlu menyimpan satu set inti. Pengalihan konteks biasanya cukup mahal sehingga penyimpanan / pemulihan ekstra tidak menghabiskan banyak biaya dibandingkan dengan semua birokrasi lainnya. SPARC sebenarnya bekerja di sekitar ini dengan membuat bank register sebagai "jendela" pada area memori, jadi ini agak berskala dengan ini (semacam lambaian tangan).
John Ripley
4
Pertimbangkan pikiran saya terpesona oleh jawaban yang begitu menyeluruh yang saya yakin tidak menyangka. Juga, terima kasih atas penjelasan tentang mengapa kita tidak benar-benar membutuhkan register bernama sebanyak itu, itu sangat menarik! Saya sangat menikmati membaca jawaban Anda, karena saya sangat tertarik dengan apa yang terjadi "di balik terpal". :) Saya akan menunggu lebih lama sebelum menerima jawaban, karena Anda tidak pernah tahu, tetapi +1 saya pasti.
Xeo
1
terlepas dari di mana tanggung jawab untuk menyimpan register, waktu yang dibutuhkan adalah overhead administrasi. Oke, jadi peralihan konteks mungkin bukan kasus yang paling sering terjadi, tetapi interupsi adalah. Rutinitas kode tangan dapat menghemat register tetapi jika driver ditulis dalam C kemungkinan besar fungsi yang dideklarasikan interupsi akan menyimpan setiap register, memanggil isr dan kemudian memulihkan semua register yang disimpan. IA-32 memiliki keunggulan interupsi dengan 15-20 regs dibandingkan dengan 32+ regs arsitektur RISC.
Olof Forshell
1
Jawaban yang sangat bagus, tapi saya tidak akan setuju dengan perbandingan langsung dari register "berganti nama" dengan yang adressable "asli". Pada x86-32, bahkan dengan 256 register internal, Anda tidak dapat menggunakan lebih dari 8 nilai sementara yang disimpan dalam register dalam satu titik eksekusi. Pada dasarnya, penggantian nama register hanyalah produk sampingan OOE yang aneh, tidak lebih.
noop
12

Kami Lakukan Memiliki Lebih dari Mereka

Karena hampir setiap instruksi harus memilih 1, 2, atau 3 register yang secara arsitektural terlihat, menambah jumlah dari mereka akan meningkatkan ukuran kode beberapa bit pada setiap instruksi dan dengan demikian mengurangi kerapatan kode. Ini juga meningkatkan jumlah konteks yang harus disimpan sebagai status utas, dan sebagian disimpan dalam catatan aktivasi fungsi . Operasi ini sering terjadi. Saling kunci pipeline harus memeriksa papan skor untuk setiap register dan ini memiliki kompleksitas ruang dan waktu kuadrat. Dan mungkin alasan terbesar hanyalah kompatibilitas dengan set instruksi yang sudah ditentukan.

Tapi ternyata, berkat penggantian nama register , kita benar-benar memiliki banyak register yang tersedia, dan kita bahkan tidak perlu menyimpannya. CPU sebenarnya memiliki banyak set register, dan secara otomatis beralih di antara mereka saat kode Anda dijalankan. Ini dilakukan murni untuk memberi Anda lebih banyak register.

Contoh:

load  r1, a  # x = a
store r1, x
load  r1, b  # y = b
store r1, y

Dalam arsitektur yang hanya memiliki r0-r7, kode berikut dapat ditulis ulang secara otomatis oleh CPU sebagai sesuatu seperti:

load  r1, a
store r1, x
load  r10, b
store r10, y

Dalam hal ini r10 adalah register tersembunyi yang menggantikan r1 untuk sementara. CPU dapat mengetahui bahwa nilai r1 tidak pernah digunakan lagi setelah penyimpanan pertama. Hal ini memungkinkan pemuatan pertama ditunda (bahkan cache pada chip biasanya memerlukan beberapa siklus) tanpa memerlukan penundaan pemuatan kedua atau penyimpanan kedua.

DigitalRoss
sumber
2

Mereka menambahkan register sepanjang waktu, tetapi sering kali terkait dengan instruksi tujuan khusus (mis. SIMD, SSE2, dll) atau memerlukan kompilasi ke arsitektur CPU tertentu, yang menurunkan portabilitas. Instruksi yang ada sering kali berfungsi pada register tertentu dan tidak dapat memanfaatkan register lain jika tersedia. Set instruksi lama dan semuanya.

Seth Robertson
sumber
1

Untuk menambahkan sedikit info menarik di sini, Anda akan melihat bahwa memiliki 8 register berukuran sama memungkinkan opcode untuk menjaga konsistensi dengan notasi heksadesimal. Misalnya instruksinya push axadalah opcode 0x50 pada x86 dan naik ke 0x57 untuk register terakhir di. Kemudian instruksi pop axdimulai pada 0x58 dan naik ke 0x5F pop diuntuk menyelesaikan basis-16 pertama. Konsistensi heksadesimal dipertahankan dengan 8 register per ukuran.


sumber
2
Pada x86 / 64 prefiks instruksi REX memperluas indeks register dengan lebih banyak bit.
Alexey Frunze