คุณตกเป็นเหยื่อของการทำนายสาขาล้มเหลว
การทำนายสาขาคืออะไร
พิจารณาทางแยกรถไฟ:
ภาพโดย Mecanismo ผ่าน Wikimedia Commons ใช้ภายใต้ใบอนุญาตCC-By-SA 3.0
ตอนนี้เพื่อการโต้แย้งสมมติว่านี่เป็นยุค 1800 - ก่อนการสื่อสารทางไกลหรือวิทยุ
คุณเป็นผู้ดำเนินการของทางแยกและคุณได้ยินเสียงรถไฟมา คุณไม่รู้ว่าจะไปทางไหนดี คุณหยุดรถไฟเพื่อถามคนขับว่าต้องการทิศทางใด จากนั้นให้คุณตั้งสวิตช์อย่างเหมาะสม
รถไฟมีน้ำหนักมากและมีความเฉื่อยมากมาย ดังนั้นพวกเขาจึงใช้เวลาตลอดไปในการเริ่มต้นและทำให้ช้าลง
มีวิธีที่ดีกว่า? คุณเดาทิศทางของรถไฟที่จะไป!
- หากคุณเดาถูกมันก็จะดำเนินต่อไป
- หากคุณเดาผิดกัปตันจะหยุดสำรองและตะโกนใส่คุณเพื่อสลับสวิตช์ จากนั้นสามารถรีสตาร์ทลงเส้นทางอื่น
หากคุณเดาถูกทุกครั้งรถไฟจะไม่หยุด
หากคุณเดาผิดบ่อยเกินไปรถไฟจะใช้เวลาหยุดเยอะสำรองและเริ่มใหม่
พิจารณา if-statement:ที่ระดับโปรเซสเซอร์มันเป็นคำสั่งสาขา:
คุณเป็นโปรเซสเซอร์และคุณเห็นสาขา คุณไม่รู้ว่ามันจะไปทางไหน คุณทำอะไร? คุณหยุดการทำงานและรอจนกว่าคำแนะนำก่อนหน้านี้จะเสร็จสมบูรณ์ จากนั้นคุณดำเนินการต่อเส้นทางที่ถูกต้อง
โปรเซสเซอร์สมัยใหม่มีความซับซ้อนและมีท่อยาว ดังนั้นพวกเขาจึงใช้เวลาตลอดไปในการ "อุ่นเครื่อง" และ "ชะลอตัว"
มีวิธีที่ดีกว่า? คุณเดาทิศทางที่สาขาจะไป!
- หากคุณเดาถูกคุณจะยังคงดำเนินการต่อ
- หากคุณเดาผิดคุณต้องล้างท่อและม้วนกลับไปที่สาขา จากนั้นคุณสามารถรีสตาร์ทเส้นทางอื่น
หากคุณเดาถูกทุกครั้งการประหารชีวิตจะไม่ต้องหยุด
หากคุณเดาผิดบ่อยเกินไปคุณจะใช้เวลามากในการถ่วงเวลาย้อนกลับและเริ่มต้นใหม่
นี่คือการทำนายสาขา ฉันยอมรับว่ามันไม่ใช่การเปรียบเทียบที่ดีที่สุดเนื่องจากรถไฟสามารถส่งสัญญาณทิศทางด้วยธง แต่ในคอมพิวเตอร์หน่วยประมวลผลไม่รู้ทิศทางใดที่สาขาจะไปจนถึงช่วงเวลาสุดท้าย
ดังนั้นคุณจะเดาอย่างมีกลยุทธ์เพื่อลดจำนวนครั้งที่รถไฟจะต้องสำรองและเดินไปอีกเส้นทางหนึ่งได้อย่างไร? คุณดูประวัติที่ผ่านมา! หากรถไฟเหลือ 99% ของเวลาคุณคาดเดาไปทางซ้าย ถ้ามันสลับกันคุณจะสลับการเดาของคุณ ถ้ามันไปทางเดียวทุก ๆ สามครั้งคุณก็เดาเหมือนกัน ...
คุณพยายามระบุรูปแบบและปฏิบัติตาม นี่เป็นวิธีการทำงานของเครื่องมือพยากรณ์สาขามากหรือน้อย
แอพพลิเคชั่นส่วนใหญ่มีสาขาที่ประพฤติดี ดังนั้นตัวพยากรณ์สาขาที่ทันสมัยมักจะบรรลุอัตราการเข้าชมมากกว่า 90% แต่เมื่อต้องเผชิญกับสาขาที่ไม่สามารถคาดเดาได้ซึ่งไม่มีรูปแบบที่เป็นที่รู้จักตัวพยากรณ์สาขาจะไร้ประโยชน์
อ่านเพิ่มเติม: "สาขาทำนาย" บทความเกี่ยวกับวิกิพีเดีย
ดังที่ได้กล่าวไว้ข้างต้นผู้ร้ายคือข้อความสั่ง if:
if (data[c] >= 128)
sum += data[c];
ขอให้สังเกตว่าข้อมูลจะถูกกระจายอย่างเท่าเทียมกันระหว่าง 0 และ 255 เมื่อเรียงลำดับข้อมูลประมาณครึ่งแรกของการทำซ้ำจะไม่ป้อน if-statement หลังจากนั้นพวกเขาทั้งหมดจะเข้าสู่คำสั่ง if
นี่เป็นมิตรกับผู้ทำนายสาขาเนื่องจากสาขาไปในทิศทางเดียวกันหลายต่อหลายครั้ง แม้แต่ตัวนับอิ่มตัวแบบง่าย ๆ ก็สามารถทำนายสาขาได้อย่างถูกต้องยกเว้นการวนซ้ำสองสามครั้งหลังจากที่มันเปลี่ยนทิศทาง
การสร้างภาพข้อมูลอย่างรวดเร็ว:
T = branch taken
N = branch not taken
data[] = 0, 1, 2, 3, 4, ... 126, 127, 128, 129, 130, ... 250, 251, 252, ...
branch = N N N N N ... N N T T T ... T T T ...
= NNNNNNNNNNNN ... NNNNNNNTTTTTTTTT ... TTTTTTTTTT (easy to predict)
อย่างไรก็ตามเมื่อข้อมูลเป็นแบบสุ่มสมบูรณ์ตัวพยากรณ์สาขาจะไม่แสดงผลเนื่องจากไม่สามารถทำนายข้อมูลแบบสุ่มได้ ดังนั้นอาจมีการคาดคะเนผิดประมาณ 50% (ไม่ดีไปกว่าการคาดเดาแบบสุ่ม)
data[] = 226, 185, 125, 158, 198, 144, 217, 79, 202, 118, 14, 150, 177, 182, 133, ...
branch = T, T, N, T, T, T, T, N, T, N, N, T, T, T, N ...
= TTNTTTTNTNNTTTN ... (completely random - hard to predict)
ดังนั้นสิ่งที่สามารถทำได้?
หากคอมไพเลอร์ไม่สามารถปรับสาขาให้เหมาะกับการย้ายตามเงื่อนไขคุณสามารถลองแฮ็กบางอย่างถ้าคุณเต็มใจที่จะเสียสละความสามารถในการอ่านเพื่อประสิทธิภาพ
แทนที่:
if (data[c] >= 128)
sum += data[c];
ด้วย:
int t = (data[c] - 128) >> 31;
sum += ~t & data[c];
สิ่งนี้จะกำจัดสาขาและแทนที่ด้วยการดำเนินการระดับบิตบางอย่าง
(โปรดทราบว่าการแฮ็คนี้ไม่เทียบเท่ากับคำสั่ง if-original อย่างเคร่งครัด แต่ในกรณีนี้ใช้ได้กับค่าอินพุตทั้งหมดของdata[]
)
มาตรฐาน: Core i7 920 @ 3.5 GHz
C ++ - Visual Studio 2010 - x64 ที่วางจำหน่าย
// Branch - Random
seconds = 11.777
// Branch - Sorted
seconds = 2.352
// Branchless - Random
seconds = 2.564
// Branchless - Sorted
seconds = 2.587
Java - NetBeans 7.1.1 JDK 7 - x64
// Branch - Random
seconds = 10.93293813
// Branch - Sorted
seconds = 5.643797077
// Branchless - Random
seconds = 3.113581453
// Branchless - Sorted
seconds = 3.186068823
ข้อสังเกต:
- กับสาขา:มีความแตกต่างอย่างมากระหว่างข้อมูลที่เรียงและไม่เรียงลำดับ
- ด้วยการแฮก:ไม่มีความแตกต่างระหว่างข้อมูลที่เรียงลำดับและไม่เรียงลำดับ
- ในกรณี C ++ การแฮ็คจะช้ากว่าการแบรนช์เมื่อข้อมูลถูกเรียง
กฎทั่วไปของหัวแม่มือคือการหลีกเลี่ยงการแยกสาขาขึ้นอยู่กับข้อมูลในลูปที่สำคัญ
ปรับปรุง:
GCC 4.6.1 พร้อม-O3
หรือ-ftree-vectorize
x64 สามารถสร้างการย้ายแบบมีเงื่อนไข ดังนั้นจึงไม่มีความแตกต่างระหว่างข้อมูลที่เรียงและไม่เรียงลำดับ - ทั้งสองอย่างรวดเร็ว
(หรือค่อนข้างเร็ว: สำหรับกรณีที่เรียงลำดับแล้วcmov
อาจช้าลงโดยเฉพาะถ้า GCC วางไว้บนเส้นทางวิกฤติแทนที่จะเป็นเพียงadd
โดยเฉพาะอย่างยิ่งใน Intel ก่อน Broadwell ที่cmov
มีเวลาแฝงอยู่2 รอบ: การตั้งค่า gcc optimization -O3 ทำให้โค้ดช้ากว่า -O2 )
VC ++ 2010 /Ox
เป็นไม่สามารถสร้างเงื่อนไขย้ายสาขานี้แม้ภายใต้
Intel C ++ Compiler (ICC) 11 ทำสิ่งอัศจรรย์ มันทำการแลกเปลี่ยนสองลูปดังนั้นจึงยกสาขาที่ไม่สามารถคาดเดาได้ให้กับลูปด้านนอก ดังนั้นไม่เพียง แต่จะรอดพ้นจากความผิดพลาดเท่านั้น แต่ยังเร็วเป็นสองเท่าของสิ่งที่ VC ++ และ GCC สามารถสร้างได้! กล่าวอีกนัยหนึ่ง ICC ใช้ประโยชน์จากการทดสอบลูปเพื่อเอาชนะมาตรฐาน ...
หากคุณให้รหัสที่ไม่มีสาขาของ Intel คอมไพเลอร์มันจะแสดงเวกเตอร์ที่ถูกต้อง ... และเร็วพอ ๆ กับสาขา (ที่มีการแลกเปลี่ยนลูป)
สิ่งนี้แสดงให้เห็นว่าแม้คอมไพเลอร์สมัยใหม่ที่พัฒนาแล้วจะแตกต่างกันอย่างมากในความสามารถในการปรับโค้ดให้เหมาะสม ...