คู่มือการเพิ่มประสิทธิภาพของ Agner Fogนั้นยอดเยี่ยม เขามีคู่มือตารางเวลาการสอนและเอกสารเกี่ยวกับ microarchitecture ของการออกแบบซีพียู x86 ล่าสุดทั้งหมด (ย้อนกลับไปจนถึง Intel Pentium) ดูแหล่งข้อมูลอื่น ๆ ที่เชื่อมโยงจาก/programming//tags/x86/info
เพื่อความสนุกฉันจะตอบคำถามบางข้อ (ตัวเลขจากซีพียู Intel ล่าสุด) ทางเลือกของ ops ไม่ใช่ปัจจัยสำคัญในการปรับแต่งโค้ด (เว้นแต่คุณจะสามารถหลีกเลี่ยงการหารได้)
CPU จะช้าลงกว่าการเพิ่มหรือไม่
ใช่ (เว้นแต่ว่าจะใช้กำลัง 2) (ความล่าช้าในการรับส่งข้อมูลประมาณ 3-4 เท่าโดยมีเพียงหนึ่งครั้งต่อการรับส่งสัญญาณของ Intel) อย่าพยายามหลีกเลี่ยงที่จะหลีกเลี่ยงมันเพราะมันเร็วพอที่จะเพิ่ม 2 หรือ 3
คุณลักษณะความเร็วของคณิตศาสตร์พื้นฐานและการควบคุมการไหลของ opcodes คืออะไร
ดูตารางการเรียนการสอนของ Agner Fog และคู่มือ microar Architecture หากคุณต้องการทราบอย่างแน่นอน : P ระวังการกระโดดแบบมีเงื่อนไข การกระโดดแบบไม่มีเงื่อนไข (เช่นการเรียกใช้ฟังก์ชัน) มีค่าใช้จ่ายเล็กน้อย แต่ไม่มาก
หากสอง opcodes ใช้จำนวนรอบเท่ากันในการดำเนินการดังนั้นทั้งสองสามารถใช้แทนกันได้โดยไม่มีการเพิ่มหรือลดประสิทธิภาพใด ๆ ?
ไม่, พวกเขาอาจแย่งพอร์ตการทำงานเดียวกันกับอย่างอื่น, หรืออาจจะไม่ ขึ้นอยู่กับว่าเครือข่ายพึ่งพาอื่นใดที่ CPU สามารถทำงานแบบขนานได้ (ในทางปฏิบัติมักจะไม่มีการตัดสินใจใด ๆ ที่เป็นประโยชน์ในบางครั้งมันเกิดขึ้นได้ว่าคุณสามารถใช้ vector shift หรือ vector shuffle ซึ่งทำงานบนพอร์ตที่แตกต่างกันบน Intel CPUs แต่ shift-by-bytes ของรีจิสเตอร์ทั้งหมดPSLLDQ
ฯลฯ ) ทำงานในหน่วยสับเปลี่ยน)
รายละเอียดทางเทคนิคอื่น ๆ ที่คุณสามารถแบ่งปันเกี่ยวกับประสิทธิภาพของ CPU x86 นั้นได้รับการชื่นชม
เอกสารขนาดเล็กของ Agner Fog อธิบายถึงท่อของซีพียู Intel และ AMD ในรายละเอียดเพียงพอที่จะคำนวณจำนวนรอบของลูปต่อการวนซ้ำและว่าคอขวดคือปริมาณงานที่เพิ่มขึ้นห่วงโซ่การพึ่งพาหรือการช่วงชิงสำหรับพอร์ตดำเนินการหนึ่งพอร์ต เห็นบางส่วนของคำตอบของฉันใน StackOverflow เช่นนี้หรืออย่างใดอย่างหนึ่ง
นอกจากนี้http://www.realworldtech.com/haswell-cpu/ (และคล้ายกันสำหรับการออกแบบก่อนหน้านี้) คือการอ่านที่สนุกถ้าคุณชอบการออกแบบ CPU
นี่คือรายการของคุณเรียงลำดับสำหรับ Haswell CPU ตามผู้เยี่ยมชมที่ดีที่สุดของฉัน นี่ไม่ใช่วิธีที่มีประโยชน์ในการคิดเกี่ยวกับสิ่งต่าง ๆ แต่เป็นการปรับแต่ง asm loop เอฟเฟกต์การคาดคะเนแคช / สาขามักจะครองดังนั้นเขียนโค้ดของคุณให้มีรูปแบบที่ดี ตัวเลขเป็นคลื่นมือและพยายามคิดค่าเวลาแฝงที่สูงถึงแม้ว่าปริมาณงานจะไม่เป็นปัญหาหรือสำหรับการสร้าง uops เพิ่มเติมซึ่งอุดตันท่อสำหรับสิ่งอื่น ๆ ที่จะเกิดขึ้นในแบบคู่ขนาน Esp หมายเลขแคช / สาขาทำขึ้นมาก ความหน่วงแฝงนั้นมีความสำคัญสำหรับการพึ่งพาแบบวนรอบ
TL: DR ตัวเลขเหล่านี้ประกอบขึ้นจากสิ่งที่ฉันนึกภาพสำหรับกรณีการใช้งาน "ทั่วไป" เท่าที่การแลกเปลี่ยนระหว่างความหน่วงแฝงคอขวดการดำเนินการพอร์ตและปริมาณงานส่วนหน้า (หรือแผงลอยสำหรับสิ่งต่าง ๆ เช่นสาขาพลาด ) กรุณาอย่าใช้ตัวเลขเหล่านี้ชนิดของการวิเคราะห์ perf ร้ายแรงใด ๆ
- 0.5 ถึง 1 Bitwise / จำนวนเต็มบวก / ลบ /
เปลี่ยนและหมุน (นับ const เวลารวบรวม) /
รุ่นเวกเตอร์ของทั้งหมดเหล่านี้ (1 ถึง 4 ต่อปริมาณงานรอบ, 1 รอบแฝง)
- 1 นาทีของเวกเตอร์, สูงสุด, เท่ากับการเปรียบเทียบ, เปรียบเทียบมากขึ้น (เพื่อสร้างมาสก์)
- 1.5 เวกเตอร์สับ Haswell และใหม่กว่ามีพอร์ตสับเปลี่ยนเพียงพอร์ตเดียวและสำหรับฉันมันเป็นเรื่องธรรมดาที่จะต้องมีการสับมากถ้าคุณต้องการดังนั้นฉันจึงให้น้ำหนักที่สูงขึ้นเล็กน้อย พวกเขาไม่ได้ฟรีโดยเฉพาะอย่างยิ่ง หากคุณต้องการหน้ากากควบคุม pshufb จากหน่วยความจำ
- 1.5 load / store (L1 cache hit. throughput ดีกว่า latency)
- 1.75 การรวมจำนวนเต็ม (3c latency / หนึ่งต่อ 1c tput บน Intel, 4c lat บน AMD และเพียงหนึ่งต่อ 2c tput) ค่าคงที่ขนาดเล็กแม้ราคาถูกโดยใช้หน่วยงาน LEA และ / หรือการเพิ่ม แต่แน่นอนว่าค่าคงที่เวลาคอมไพล์ยังดีอยู่เสมอและสามารถปรับให้เหมาะกับสิ่งอื่น ๆ ได้ (และทวีคูณในลูปมักจะสามารถลดความแข็งแรงของคอมไพเลอร์
tmp += 7
เป็นลูปแทนtmp = i*7
)
- 1.75 สับเปลี่ยนเวกเตอร์ 256b บางส่วน (เวลาแฝงเพิ่มเติมในส่วนที่สามารถย้ายข้อมูลระหว่าง 128b เลนของเวกเตอร์ AVX) (หรือ 3 ถึง 7 บน Ryzen ซึ่งการสับเปลี่ยนเลนจะต้องใช้ uop อื่น ๆ อีกมากมาย)
- 2 fp เพิ่ม / ย่อย (และเวอร์ชันเวกเตอร์ที่เหมือนกัน) (1 หรือ 2 ต่อทรูพุตรอบ, 3 ถึง 5 รอบแฝง) อาจช้าถ้าคุณติดขัดเรื่องเวลาแฝงเช่นการรวมอาเรย์กับ
sum
ตัวแปรเพียง 1 ตัว (ฉันสามารถน้ำหนักนี้และ fp mul ต่ำสุดที่ 1 หรือสูงถึง 5 ขึ้นอยู่กับการใช้งานกรณี)
- 2 เวกเตอร์ fp mul หรือ FMA (x * y + z มีราคาถูกเท่ากับ mul หรือ add ถ้าคุณคอมไพล์ด้วยการรองรับ FMA ที่เปิดใช้งาน)
- 2 การแทรก / แยกการลงทะเบียนวัตถุประสงค์ทั่วไปลงในองค์ประกอบเวกเตอร์ (
_mm_insert_epi8
ฯลฯ )
- 2.25 เวกเตอร์ int mul (องค์ประกอบ 16 บิตหรือ pmaddubsw ทำ 8 * 8 -> 16 บิต) ถูกกว่าบน Skylake ด้วยปริมาณงานที่ดีกว่า scalar mul
- 2.25 shift / หมุนโดยการนับตัวแปร (2c latency หนึ่งต่อ 2c throughput บน Intel, เร็วกว่าบน AMD หรือ BMI2)
- 2.5 การเปรียบเทียบโดยไม่มีการแยกสาขา (
y = x ? a : b
หรือy = x >= 0
) ( test / setcc
หรือcmov
)
- 3 int-> การแปลงลอย
- 3 คาดการณ์อย่างสมบูรณ์แบบโฟลว์ควบคุม (สาขาที่ถูกทำนาย, โทร, ส่งคืน)
- 4 vector int mul (องค์ประกอบ 32- บิต) (2 uops, 10c latency บน Haswell)
- 4 การหารจำนวนเต็มหรือ
%
ค่าคงที่เวลาคอมไพล์ (ไม่ใช่พลังงานของ 2)
- ops แนวนอน 7 เวกเตอร์ (เช่น
PHADD
การเพิ่มค่าภายในเวกเตอร์)
- 11 (vector) FP Division (เวลาแฝง 10-13c หนึ่งครั้งต่อ 7c throughput หรือแย่กว่านั้น) (อาจมีราคาถูกถ้าใช้น้อย แต่ปริมาณงานคือ 6 ถึง 40x แย่กว่า FP mul)
- 13? Control Flow (สาขาที่คาดคะเนไม่ดีอาจคาดการณ์ได้ 75%)
- 13 การหาร int ( ใช่จริง ๆมันช้ากว่าการหาร FP และไม่สามารถ vectorize ได้) (โปรดทราบว่าคอมไพเลอร์หารด้วยค่าคงที่โดยใช้ mul / shift / add กับค่าคงที่เวทย์มนตร์และ div / mod โดยพลังของ 2 มีราคาถูกมาก)
- 16 (เวกเตอร์) FP sqrt
- 25? โหลด (กด L3 แคช) (ร้านค้าแคชแคชมีราคาถูกกว่าโหลด)
- 50? FP trig / exp / log หากคุณต้องการ exp / log จำนวนมากและไม่ต้องการความแม่นยำเต็มที่คุณสามารถแลกเปลี่ยนความแม่นยำเพื่อความเร็วด้วยพหุนามและ / หรือตารางที่สั้นลง คุณยังสามารถ SIMD เวกเตอร์
- 50-80? สาขาที่คาดการณ์ไว้เสมอมีค่าใช้จ่ายประมาณ 15-20 รอบ
- 200-400? โหลด / จัดเก็บ (แคชมิส)
- 3000 ??? อ่านหน้าจากไฟล์ (กดแคชดิสก์ OS) (ทำตัวเลขที่นี่)
- 20000 ??? หน้าการอ่านดิสก์ (มิสแคชของระบบปฏิบัติการ, SSD เร็ว) (หมายเลขที่สร้างขึ้นทั้งหมด)
ฉันทั้งหมดนี้ทำขึ้นบนพื้นฐานของการคาดเดา หากมีบางอย่างผิดปกติอาจเป็นเพราะฉันคิดถึงกรณีใช้งานอื่นหรือมีข้อผิดพลาดในการแก้ไข
ต้นทุนสัมพัทธ์ของสิ่งต่าง ๆ บน AMD CPU จะคล้ายกันยกเว้นว่าจะมีจำนวน shifters ที่เร็วกว่าเมื่อการเปลี่ยนแปลงนับเป็นตัวแปร ซีพียูตระกูล AMD Bulldozer นั้นช้ากว่าโค้ดส่วนใหญ่ด้วยเหตุผลหลายประการ (Ryzen ค่อนข้างดีในเรื่องต่าง ๆ มากมาย)
เก็บไว้ในใจว่ามันเป็นจริงเป็นไปไม่ได้ที่จะต้มลงไปสิ่งที่มีค่าใช้จ่ายหนึ่งมิติ นอกเหนือจากการคิดถึงแคชและการคาดคะเนสาขาจะมีความผิดพลาดคอขวดในบล็อกของโค้ดอาจเป็นเวลาแฝงปริมาณงาน uop ทั้งหมด (ส่วนหน้า) หรือปริมาณงานของพอร์ตเฉพาะ (พอร์ตเรียกใช้งาน)
การดำเนินการ "ช้า" ชอบส่วน FP สามารถถูกมากถ้ารหัสโดยรอบช่วยให้ซีพียูยุ่งกับการทำงานอื่น ๆ (vector FP div หรือ sqrt คือ 1 uop แต่ละอันมี latency และ throughput ไม่ดีพวกเขาบล็อกเฉพาะหน่วยหารไม่ใช่พอร์ตการดำเนินการทั้งหมดที่เปิดอยู่ Integer div เป็นหลาย uops) ดังนั้นถ้าคุณมี FP หารเดียว สำหรับทุก ๆ 20 ~ 20 โมงและเพิ่มและมีงานอื่น ๆ สำหรับซีพียูที่ต้องทำ (เช่นการวนซ้ำแบบวนซ้ำอย่างอิสระ) จากนั้น "ต้นทุน" ของ FP div อาจจะเหมือนกับ FP FP นี่อาจเป็นตัวอย่างที่ดีที่สุดของบางสิ่งบางอย่างที่ทรูพุตต่ำเมื่อมันทั้งหมดที่คุณกำลังทำอยู่ แต่เข้ากันได้ดีกับโค้ดอื่น ๆ (เมื่อเวลาแฝงไม่ใช่ปัจจัย) เนื่องจากยูเอฟโอรวมต่ำ
โปรดทราบว่าการแบ่งจำนวนเต็มนั้นไม่ใกล้เคียงกับรหัสโดยรอบ: ใน Haswell มันคือ 9 uops โดยมีหนึ่งต่อ 8-11c throughput และ 22-29c latency (การแบ่ง 64 บิตช้ากว่ามากแม้แต่ใน Skylake) ดังนั้นเวลาในการตอบสนองและปริมาณงานจึงค่อนข้างคล้ายกับ FP div แต่ FP div เป็นเพียงหนึ่งหน่วยเท่านั้น
สำหรับตัวอย่างของการวิเคราะห์ลำดับสั้น ๆ ของ insns สำหรับ throughput, latency, และ uops ทั้งหมด, ดูคำตอบ SO ของฉันบางส่วน:
IDK ถ้าคนอื่นเขียนคำตอบ SO รวมถึงการวิเคราะห์แบบนี้ ฉันมีเวลาค้นหาตัวเองได้ง่ายขึ้นมากเพราะฉันรู้ว่าฉันจะเข้าไปดูรายละเอียดนี้บ่อยครั้งและฉันจำสิ่งที่ฉันเขียนได้