ถ้าการลงทะเบียนเร็วมากทำไมเราไม่มีมากกว่านี้ล่ะ?


89

ใน 32 บิตเรามีการลงทะเบียน "วัตถุประสงค์ทั่วไป" 8 รายการ ด้วย 64 บิตจำนวนเงินจะเพิ่มขึ้นเป็นสองเท่า แต่ดูเหมือนว่าจะไม่ขึ้นอยู่กับการเปลี่ยนแปลง 64 บิตเอง
ตอนนี้ถ้าการลงทะเบียนเร็วมาก (ไม่มีการเข้าถึงหน่วยความจำ) ทำไมถึงไม่มีการลงทะเบียนมากกว่านี้? ผู้สร้าง CPU ไม่ควรลงทะเบียนใน CPU ให้มากที่สุดเท่าที่จะทำได้หรือไม่? อะไรคือข้อ จำกัด เชิงตรรกะที่ทำให้เรามีเพียงจำนวนเงินที่เรามี?


ซีพียูและ GPU ซ่อนเวลาแฝงเป็นหลักโดยแคชและมัลติเธรดขนาดใหญ่ตามลำดับ ดังนั้นซีพียูจึงมี (หรือต้องการ) รีจิสเตอร์น้อยในขณะที่ GPU มีรีจิสเตอร์นับหมื่น ดูเอกสารการสำรวจของฉันในไฟล์ทะเบียน GPUซึ่งกล่าวถึงการแลกเปลี่ยนและปัจจัยเหล่านี้ทั้งหมด
user984260

คำตอบ:


120

มีหลายเหตุผลที่คุณไม่เพียงแค่มีการลงทะเบียนจำนวนมาก:

  • พวกเขาเชื่อมโยงอย่างมากกับขั้นตอนไปป์ไลน์ส่วนใหญ่ สำหรับผู้เริ่มต้นคุณต้องติดตามอายุการใช้งานและส่งต่อผลลัพธ์กลับไปยังขั้นตอนก่อนหน้า ความซับซ้อนว่ายากขึ้นอย่างรวดเร็วและจำนวนสายไฟ (ตามตัวอักษร) ที่เกี่ยวข้องเพิ่มขึ้นในอัตราเดียวกัน มีราคาแพงในพื้นที่ซึ่งในที่สุดก็หมายความว่าราคาแพงในด้านพลังงานราคาและประสิทธิภาพหลังจากจุดหนึ่ง
  • ต้องใช้พื้นที่ในการเข้ารหัสคำสั่ง 16 รีจิสเตอร์ใช้เวลา 4 บิตสำหรับต้นทางและปลายทางและอีก 4 ตัวหากคุณมีคำสั่ง 3 ตัวดำเนินการ (เช่น ARM) นั่นเป็นพื้นที่เข้ารหัสชุดคำสั่งที่แย่มากที่ใช้เพื่อระบุรีจิสเตอร์ ในที่สุดสิ่งนี้ส่งผลกระทบต่อการถอดรหัสขนาดโค้ดและความซับซ้อนอีกครั้ง
  • มีวิธีที่ดีกว่าเพื่อให้ได้ผลลัพธ์เดียวกัน ...

ทุกวันนี้เรามีการลงทะเบียนมากมาย - พวกเขาไม่ได้ตั้งโปรแกรมไว้อย่างชัดเจน เรามี "การเปลี่ยนชื่อทะเบียน" แม้ว่าคุณจะเข้าถึงเพียงชุดเล็ก ๆ (8-32 รีจิสเตอร์) แต่จริงๆแล้วมันได้รับการสนับสนุนจากชุดใหญ่กว่ามาก (เช่น 64-256) จากนั้น CPU จะติดตามการมองเห็นของแต่ละรีจิสเตอร์และจัดสรรให้กับชุดที่เปลี่ยนชื่อ ตัวอย่างเช่นคุณสามารถโหลดแก้ไขแล้วจัดเก็บลงทะเบียนได้หลายครั้งติดต่อกันและให้แต่ละการดำเนินการเหล่านี้ดำเนินการโดยอิสระขึ้นอยู่กับแคชที่พลาดเป็นต้นใน ARM:

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

คอร์ Cortex A9 ทำการรีจิสเตอร์การเปลี่ยนชื่อดังนั้นการโหลดครั้งแรกเป็น "r0" จะไปที่รีจิสเตอร์เสมือนที่เปลี่ยนชื่อ - เรียกว่า "v0" การโหลดการเพิ่มและการจัดเก็บเกิดขึ้นใน "v0" ในขณะเดียวกันเรายังทำการโหลด / แก้ไข / จัดเก็บเป็น r0 อีกครั้ง แต่จะเปลี่ยนชื่อเป็น "v1" เนื่องจากเป็นลำดับอิสระทั้งหมดโดยใช้ r0 สมมติว่าโหลดจากตัวชี้ใน "r4" หยุดทำงานเนื่องจากแคชพลาด ไม่เป็นไร - เราไม่จำเป็นต้องรอให้ "r0" พร้อม เนื่องจากมีการเปลี่ยนชื่อเราสามารถเรียกใช้ลำดับถัดไปด้วย "v1" (แมปกับ r0 ด้วย) - และบางทีนั่นอาจเป็นการโจมตีแคชและเราเพิ่งชนะการแสดงครั้งใหญ่

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

ฉันคิดว่า x86 ขึ้นอยู่กับจำนวนการลงทะเบียนที่เปลี่ยนชื่อในปัจจุบัน (ballpark 256) นั่นหมายความว่ามี 8 บิตคูณ 2 สำหรับทุกคำสั่งเพียงเพื่อบอกว่าต้นทางและปลายทางคืออะไร มันจะเพิ่มจำนวนสายไฟที่จำเป็นอย่างมากในแกนกลางและขนาดของมัน ดังนั้นจึงมีจุดที่น่าสนใจประมาณ 16-32 รีจิสเตอร์ที่นักออกแบบส่วนใหญ่ตัดสินและสำหรับการออกแบบ CPU ที่ไม่เป็นไปตามลำดับการเปลี่ยนชื่อการลงทะเบียนเป็นวิธีที่จะลดลง

แก้ไข : ความสำคัญของการดำเนินการนอกคำสั่งและการลงทะเบียนการเปลี่ยนชื่อในเรื่องนี้ เมื่อคุณมี OOO แล้วจำนวนการลงทะเบียนไม่สำคัญมากนักเนื่องจากเป็นเพียง "แท็กชั่วคราว" และเปลี่ยนชื่อเป็นชุดทะเบียนเสมือนที่ใหญ่กว่ามาก คุณไม่ต้องการให้ตัวเลขมีขนาดเล็กเกินไปเพราะจะเขียนลำดับโค้ดขนาดเล็กได้ยาก นี่เป็นปัญหาสำหรับ x86-32 เนื่องจากรีจิสเตอร์ 8 ตัวที่ จำกัด หมายถึงจำนวนจังหวะที่จบลงด้วยการผ่านสแต็กและคอร์ต้องการตรรกะเพิ่มเติมเพื่อส่งต่อการอ่าน / เขียนไปยังหน่วยความจำ หากคุณไม่มี OOO คุณมักจะพูดถึงคอร์ขนาดเล็กซึ่งในกรณีนี้ชุดทะเบียนขนาดใหญ่จะให้ประโยชน์ด้านต้นทุน / ประสิทธิภาพที่ไม่ดี

ดังนั้นจึงมีจุดที่น่าสนใจตามธรรมชาติสำหรับขนาดของธนาคารที่ลงทะเบียนซึ่งมีการลงทะเบียนสูงสุดประมาณ 32 รายการสำหรับ CPU ส่วนใหญ่ x86-32 มี 8 รีจิสเตอร์และมันเล็กเกินไปแน่นอน ARM มีการลงทะเบียน 16 รายการและเป็นการประนีประนอมที่ดี การลงทะเบียน 32 รายการมีมากเกินไปเล็กน้อยหากมีสิ่งใด - คุณไม่ต้องการ 10 หรือมากกว่านั้น

สิ่งนี้ไม่ได้สัมผัสกับการลงทะเบียนเพิ่มเติมที่คุณได้รับสำหรับ SSE และตัวประมวลผลร่วมเวกเตอร์ลอยตัวอื่น ๆ สิ่งเหล่านี้มีความหมายว่าเป็นชุดพิเศษเนื่องจากทำงานเป็นอิสระจากแกนจำนวนเต็มและไม่ทำให้ความซับซ้อนของ CPU เพิ่มขึ้นอย่างทวีคูณ


12
คำตอบที่ยอดเยี่ยม - ฉันต้องการใช้เหตุผลอื่นในการผสมผสานยิ่งมีการลงทะเบียนมากเท่าไหร่ก็ยิ่งต้องใช้เวลามากขึ้นในการโยนลงบน / ดึงออกจากสแต็กเมื่อสลับบริบท ไม่ใช่ประเด็นสำคัญอย่างแน่นอน แต่เป็นข้อพิจารณา
Will A

7
@ จะเป็นจุดที่ดี อย่างไรก็ตามสถาปัตยกรรมที่มีการลงทะเบียนจำนวนมากมีวิธีลดค่าใช้จ่ายนี้ โดยปกติ ABI จะมี callee-save ของรีจิสเตอร์ส่วนใหญ่ดังนั้นคุณต้องบันทึกชุดหลักเท่านั้น การสลับบริบทมักจะมีราคาแพงพอที่การบันทึก / กู้คืนพิเศษจะไม่เสียค่าใช้จ่ายมากเมื่อเทียบกับเทปสีแดงอื่น ๆ ทั้งหมด SPARC ใช้งานได้จริงโดยการทำให้ธนาคารลงทะเบียนเป็น "หน้าต่าง" บนพื้นที่หน่วยความจำดังนั้นมันจึงปรับขนาดด้วยสิ่งนี้ (แบบที่โบกมือ)
John Ripley

4
พิจารณาความคิดของฉันด้วยคำตอบที่ละเอียดถี่ถ้วนซึ่งฉันไม่คาดคิด นอกจากนี้ขอบคุณสำหรับคำอธิบายว่าทำไมเราไม่ต้องการการลงทะเบียนที่มีชื่อมากมายนั่นน่าสนใจมาก! ฉันชอบอ่านคำตอบของคุณมากเพราะฉันสนใจสิ่งที่เกิดขึ้น "ภายใต้ประทุน" :) ฉันจะรออีกสักหน่อยก่อนที่จะยอมรับคำตอบเพราะคุณไม่เคยรู้ แต่ +1 ของฉันนั้นแน่นอน
Xeo

1
ไม่ว่าความรับผิดชอบในการบันทึกทะเบียนจะอยู่ที่ใดก็ตามเวลาที่ใช้คือค่าใช้จ่ายในการบริหาร ตกลงดังนั้นการสลับบริบทอาจไม่ใช่กรณีที่เกิดขึ้นบ่อยที่สุด แต่การขัดจังหวะคือ กิจวัตรที่เข้ารหัสด้วยมืออาจประหยัดในการลงทะเบียน แต่ถ้าไดรเวอร์เขียนด้วย C โอกาสที่ฟังก์ชันที่ประกาศขัดจังหวะจะบันทึกทุกการลงทะเบียนเรียกใช้ isr แล้วเรียกคืนการลงทะเบียนที่บันทึกไว้ทั้งหมด IA-32 มีข้อได้เปรียบในการขัดจังหวะโดยมี 15-20 regs เมื่อเทียบกับ 32 + บางสิ่งบางอย่างของสถาปัตยกรรม RISC
Olof Forshell

1
คำตอบที่ยอดเยี่ยม แต่ฉันจะไม่เห็นด้วยกับการเปรียบเทียบการลงทะเบียน "เปลี่ยนชื่อ" โดยตรงกับการลงทะเบียน "จริง" บน x86-32 แม้จะมีการลงทะเบียนภายใน 256 รายการ แต่คุณไม่สามารถใช้ค่าชั่วคราวมากกว่า 8 ค่าที่เก็บไว้ในรีจิสเตอร์ในจุดดำเนินการใด ๆ โดยพื้นฐานแล้วการเปลี่ยนชื่อการลงทะเบียนเป็นเพียงผลพลอยได้ที่น่าสงสัยของ OOE เท่านั้นไม่มีอะไรเพิ่มเติม
noop

12

เราทำมีมากขึ้นของพวกเขา

เนื่องจากเกือบทุกคำสั่งต้องเลือก 1, 2 หรือ 3 รีจิสเตอร์ที่มองเห็นได้ทางสถาปัตยกรรมการขยายจำนวนจะเพิ่มขนาดโค้ดขึ้นหลายบิตในแต่ละคำสั่งและลดความหนาแน่นของโค้ด นอกจากนี้ยังเพิ่มปริมาณของบริบทที่จะต้องบันทึกเป็นรัฐด้ายและบันทึกในส่วนฟังก์ชั่นของการบันทึกการเปิดใช้งาน การดำเนินการเหล่านี้เกิดขึ้นบ่อยครั้ง ลูกโซ่ไปป์ไลน์ต้องตรวจสอบป้ายบอกคะแนนสำหรับการลงทะเบียนทุกครั้งและสิ่งนี้มีความซับซ้อนของเวลากำลังสองและพื้นที่ และบางทีเหตุผลที่ใหญ่ที่สุดคือความเข้ากันได้กับชุดคำสั่งที่กำหนดไว้แล้ว

แต่ปรากฎว่าต้องขอบคุณการเปลี่ยนชื่อการลงทะเบียนทำให้เรามีทะเบียนมากมายและเราไม่จำเป็นต้องบันทึกด้วยซ้ำ จริงๆแล้ว CPU มีชุดรีจิสเตอร์หลายชุดและจะสลับไปมาระหว่างกันโดยอัตโนมัติเมื่อโค้ดของคุณทำงาน สิ่งนี้ทำเพื่อให้คุณได้รับการลงทะเบียนมากขึ้น

ตัวอย่าง:

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

ในสถาปัตยกรรมที่มีเฉพาะ r0-r7 ซีพียูอาจเขียนโค้ดต่อไปนี้ใหม่โดยอัตโนมัติดังนี้:

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

ในกรณีนี้ r10 คือรีจิสเตอร์ที่ซ่อนอยู่ซึ่งถูกแทนที่ด้วย r1 ชั่วคราว ซีพียูสามารถบอกได้ว่าค่าของ r1 จะไม่ถูกใช้อีกเลยหลังจากการจัดเก็บครั้งแรก สิ่งนี้ช่วยให้การโหลดครั้งแรกล่าช้า (แม้การกดแคชบนชิปมักจะใช้เวลาหลายรอบ) โดยไม่ต้องหน่วงเวลาของการโหลดครั้งที่สองหรือการจัดเก็บที่สอง


2

พวกเขาเพิ่มรีจิสเตอร์ตลอดเวลา แต่มักจะเชื่อมโยงกับคำสั่งวัตถุประสงค์พิเศษ (เช่น SIMD, SSE2 ฯลฯ ) หรือต้องการการคอมไพล์กับสถาปัตยกรรม CPU เฉพาะซึ่งจะช่วยลดความสามารถในการพกพา คำสั่งที่มีอยู่มักใช้ได้กับการลงทะเบียนเฉพาะและไม่สามารถใช้ประโยชน์จากการลงทะเบียนอื่น ๆ ได้หากมี ชุดคำสั่งดั้งเดิมและทั้งหมด


1

หากต้องการเพิ่มข้อมูลที่น่าสนใจเล็กน้อยที่นี่คุณจะสังเกตเห็นว่าการมีทะเบียนขนาดเดียวกัน 8 ตัวช่วยให้ opcodes รักษาความสอดคล้องกับสัญกรณ์ฐานสิบหก ตัวอย่างเช่นคำสั่งpush axคือ opcode 0x50 บน x86 และสูงถึง 0x57 สำหรับการลงทะเบียนล่าสุด จากนั้นคำสั่งpop axเริ่มต้นที่ 0x58 และขึ้นไปที่ 0x5F pop diเพื่อทำฐาน -16 แรกให้เสร็จสมบูรณ์ ความสอดคล้องของเลขฐานสิบหกจะคงไว้ด้วยการลงทะเบียน 8 รายการต่อขนาด


2
บน x86 / 64 คำนำหน้าคำสั่ง REX จะขยายดัชนีรีจิสเตอร์ที่มีบิตมากขึ้น
Alexey Frunze
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.