เคล็ดลับการเพิ่มประสิทธิภาพระดับต่ำ C ++ [ปิด]


79

สมมติว่าคุณมีอัลกอริธึมที่ดีที่สุดแล้วโซลูชันระดับต่ำสุดที่คุณสามารถเสนอได้สำหรับการบีบอัตราเฟรม Sweet Sweet สองสามหยดสุดท้ายของรหัส C ++ คืออะไร

มันไปโดยไม่บอกว่าเคล็ดลับเหล่านี้ใช้เฉพาะกับส่วนของรหัสที่สำคัญซึ่งคุณได้เน้นไว้ใน profiler ของคุณแล้ว แต่ควรเป็นการปรับปรุงที่ไม่มีโครงสร้างในระดับต่ำ ฉันได้ยกตัวอย่าง


1
สิ่งนี้ทำให้คำถามพัฒนาเกมและไม่ใช่คำถามทั่วไปเกี่ยวกับการเขียนโปรแกรมเช่น: stackoverflow.com/search?q=c%2B%2B+optimization
Danny Varod

@Danny - นี่อาจเป็นคำถามการเขียนโปรแกรมทั่วไป เป็นคำถามที่เกี่ยวข้องกับการเขียนโปรแกรมเกมอย่างแน่นอน ฉันคิดว่ามันเป็นคำถามที่ปฏิบัติได้ในทั้งสองเว็บไซต์
Smashery

@Smashery ความแตกต่างเพียงอย่างเดียวระหว่างทั้งสองคือการเขียนโปรแกรมเกมอาจต้องการการปรับแต่งระดับเอ็นจิ้นกราฟิกเฉพาะหรือการปรับแต่ง shader coder ส่วน C ++ จะเหมือนกัน
Danny Varod

@Danny - จริงคำถามบางคำถามจะมี "เพิ่มเติม" ที่เกี่ยวข้องในเว็บไซต์หนึ่งหรืออื่น ๆ ; แต่ฉันไม่ต้องการเบี่ยงเบนคำถามที่เกี่ยวข้องใด ๆ เพียงเพราะพวกเขาอาจถูกถามในเว็บไซต์อื่น
Smashery

คำตอบ:


76

ปรับโครงร่างข้อมูลของคุณให้เหมาะสม! (สิ่งนี้ใช้ได้กับภาษามากกว่าภาษา C ++)

คุณสามารถลงลึกมากเพื่อทำการปรับแต่งข้อมูลตัวประมวลผลการจัดการแบบมัลติคอร์อย่างดี ฯลฯ แต่แนวคิดพื้นฐานคือ:

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

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

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


ควรลองใช้ตัวอย่าง C ในหน้าการเชื่อมโยงกันของแคชที่เชื่อมโยง เมื่อฉันแรกพบเกี่ยวกับเรื่องนี้ฉันก็ตกใจว่ามันแตกต่างกันมากแค่ไหน
Neel

9
ดูข้อผิดพลาดที่ยอดเยี่ยมของการนำเสนอการเขียนโปรแกรมเชิงวัตถุ (Sony R&D) ( research.scee.net/files/presentations/gcapaustralia09/… ) - และบทความ CellPerformance ที่บ้าคลั่ง แต่น่าสนใจโดย Mike Acton ( cellperformance.beyond3d.com/articles/ index.html ) เกมของ Noel Llopis จากภายในบล็อกยังมีเนื้อหาเกี่ยวกับเรื่องนี้บ่อยครั้ง ( gamesfromwithin.com ) ฉันไม่สามารถแนะนำ Pitfalls สไลด์ได้เพียงพอ ...
27154 leander leander

2
ฉันต้องการเพียงแค่เตือนเกี่ยวกับ"ทำให้ข้อมูลสำหรับแต่ละซ้ำเป็นขนาดเล็กที่สุดและใกล้เคียงร่วมกันเป็นไปได้ในความทรงจำ" การเข้าถึงข้อมูลที่ไม่จัดชิดอาจทำให้ช้าลง ในกรณีที่ช่องว่างภายในจะให้ประสิทธิภาพที่ดีขึ้น การสั่งซื้อของข้อมูลเป็นข้อมูลที่ได้รับคำสั่งเกินไปเช่นกันที่สำคัญสามารถนำไปสู่การขยายน้อย Scott Mayersสามารถอธิบายสิ่งนี้ดีกว่าที่ฉันสามารถทำได้ :)
Jonathan Connell

+1 การนำเสนอของ Sony ฉันอ่านสิ่งนั้นมาก่อนและมันก็ให้ความรู้สึกถึงวิธีเพิ่มประสิทธิภาพข้อมูลในระดับแพลตฟอร์มโดยคำนึงถึงการแยกข้อมูลออกเป็นชิ้น ๆ และจัดเรียงอย่างเหมาะสม
ChrisC

84

เคล็ดลับระดับต่ำมาก ๆ แต่เป็นเคล็ดลับที่มีประโยชน์:

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

if(__builtin_expect(entity->extremely_unlikely_flag, 0)) {
  // code that is rarely run
}

ฉันเห็นความเร็ว 10-20% พร้อมการใช้งานที่เหมาะสม


1
ฉันจะลงคะแนนสองครั้งถ้าทำได้
tenpn

10
+1, เคอร์เนล Linux ใช้สิ่งนี้อย่างกว้างขวางสำหรับ microoptimizations ในรหัสตัวกำหนดตารางเวลาและมันสร้างความแตกต่างที่สำคัญในเส้นทางของรหัสที่แน่นอน
greyfade

2
แต่น่าเสียดายที่ดูเหมือนจะไม่เทียบเท่ากับ Visual Studio stackoverflow.com/questions/1440570/…
mmyers

1
ดังนั้นความถี่ที่คาดหวังมักจะต้องเป็นค่าที่ถูกต้องในการเพิ่มประสิทธิภาพ 49/50 ครั้ง หรือ 999999/1000000 ครั้ง
ดักลาส

36

สิ่งแรกที่คุณต้องเข้าใจคือฮาร์ดแวร์ที่คุณใช้อยู่ มันจัดการกับการแตกกิ่งได้อย่างไร แล้วแคชล่ะ มีชุดคำสั่ง SIMD หรือไม่ สามารถใช้โปรเซสเซอร์ได้กี่ตัว ต้องแชร์เวลาตัวประมวลผลกับสิ่งอื่นหรือไม่

คุณอาจแก้ปัญหาเดียวกันในวิธีที่แตกต่างกันมาก - แม้แต่ตัวเลือกของอัลกอริทึมของคุณควรขึ้นอยู่กับฮาร์ดแวร์ ในบางกรณี O (N) สามารถทำงานช้ากว่า O (NlogN) (ขึ้นอยู่กับการใช้งาน)

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

รายละเอียดแล้ว โปรไฟล์, โปรไฟล์, โปรไฟล์ ดูการใช้งานหน่วยความจำดูบทลงโทษการแยก, ดูค่าใช้จ่ายการเรียกใช้ฟังก์ชัน, ดูการใช้ไปป์ไลน์ หาสิ่งที่ทำให้โค้ดของคุณช้าลง อาจเป็นการเข้าถึงข้อมูล (ฉันเขียนบทความชื่อ "The Latency Elephant" เกี่ยวกับค่าใช้จ่ายในการเข้าถึงข้อมูล - google it ฉันไม่สามารถโพสต์ลิงก์ 2 ลิงก์ที่นี่เนื่องจากฉันไม่มี "ชื่อเสียง" เพียงพอ) ตรวจสอบอย่างละเอียดและ จากนั้นปรับโครงร่างข้อมูลของคุณ (อาเรย์ที่เป็นเนื้อเดียวกันขนาดใหญ่ที่ยอดเยี่ยมนั้นยอดเยี่ยมมาก ) และการเข้าถึงข้อมูล

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

หากคุณยังต้องการความเร็วมากกว่านี้ให้ขนานกันไป หากคุณได้รับประโยชน์จากการทำงานบน PS3 แล้ว SPU นั้นเป็นเพื่อนของคุณ ใช้พวกเขารักพวกเขา หากคุณเขียนโซลูชัน SIMD อยู่แล้วคุณจะได้รับผลประโยชน์มหาศาลจากการเข้า SPU

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

ไม่ว่าคุณจะอยู่บนแพลตฟอร์มใดเรียนรู้ให้มากที่สุดเท่าที่จะทำได้เกี่ยวกับฮาร์ดแวร์และโปรไฟล์ที่มีให้ อย่าคิดว่าคุณรู้ว่าคอขวดคืออะไร - หามันจาก profiler ของคุณ และให้แน่ใจว่าคุณมีฮิวริสติกเพื่อตัดสินว่าคุณทำให้เกมของคุณเร็วขึ้นจริงหรือไม่

แล้วโปรไฟล์อีกครั้ง


31

ขั้นแรก: คิดอย่างรอบคอบเกี่ยวกับข้อมูลของคุณเกี่ยวกับอัลกอริทึมของคุณ O (บันทึก n) ไม่ได้เร็วกว่า O (n) เสมอไป ตัวอย่างง่ายๆ: ตารางแฮชที่มีเพียงไม่กี่คีย์มักจะถูกแทนที่ดีกว่าด้วยการค้นหาแบบเชิงเส้น

ขั้นตอนที่สอง: ดูชุดประกอบที่สร้างขึ้น C ++ นำการสร้างรหัสโดยนัยจำนวนมากมาสู่ตาราง บางครั้งมันย่องเข้าหาคุณโดยที่คุณไม่รู้

แต่สมมติว่าเป็นเวลาถีบไปถึงโลหะจริงๆ: โปรไฟล์ อย่างจริงจัง. การใช้ "เทคนิคการปฏิบัติงาน" แบบสุ่มเกี่ยวกับโอกาสที่จะเจ็บปวดอย่างที่มันควรจะช่วย

จากนั้นทุกอย่างขึ้นอยู่กับว่าปัญหาคอขวดของคุณคืออะไร

data cache คิดถึง => ปรับโครงร่างข้อมูลของคุณให้เหมาะสม นี่เป็นจุดเริ่มต้นที่ดี: http://gamesfromwithin.com/data-oriented-design

คิดถึงรหัสแคช => ดูการเรียกฟังก์ชันเสมือนความลึก callstack ที่มากเกินไปสาเหตุที่พบบ่อยสำหรับประสิทธิภาพที่ไม่ดีคือความเชื่อที่ผิดพลาดที่คลาสพื้นฐานต้องเป็นเสมือน

C ++ ประสิทธิภาพทั่วไปอื่น ๆ

  • การจัดสรร / การจัดสรรคืนมากเกินไป หากประสิทธิภาพนั้นสำคัญอย่าเรียกใช้ในรันไทม์ เคย
  • คัดลอกสิ่งก่อสร้าง หลีกเลี่ยงทุกที่ที่คุณสามารถ ถ้ามันสามารถอ้างอิง const, ทำให้มันเป็นหนึ่ง

ทุกอย่างที่กล่าวมาข้างต้นชัดเจนเมื่อคุณดูที่ชุมนุม


19

ลบสาขาที่ไม่จำเป็น

ในบางแพลตฟอร์มและคอมไพเลอร์บางสาขาอาจทิ้งท่อทั้งหมดของคุณดังนั้นแม้ไม่มีนัยสำคัญหากบล็อก () อาจมีราคาแพง

PowerPC สถาปัตยกรรม (PS3 / X360) fselมีคำแนะนำเลือกจุดลอยตัว สิ่งนี้สามารถนำมาใช้ในสถานที่ของสาขาได้หากบล็อกนั้นเป็นงานที่ได้รับมอบหมายอย่างง่าย:

float result = 0;
if (foo > bar) { result = 2.0f; }
else { result = 1.0f; }

กลายเป็น:

float result = fsel(foo-bar, 2.0f, 1.0f);

เมื่อพารามิเตอร์แรกมากกว่าหรือเท่ากับ 0 พารามิเตอร์ที่สองจะถูกส่งกลับมิฉะนั้นเป็นพารามิเตอร์ที่สาม

ราคาของการสูญเสียสาขาคือทั้งถ้า {} และบล็อกอื่น {} จะถูกดำเนินการดังนั้นหากหนึ่งคือการดำเนินการที่มีราคาแพงหรือ dereferences ตัวชี้ NULL การเพิ่มประสิทธิภาพนี้ไม่เหมาะ

บางครั้งคอมไพเลอร์ของคุณทำงานนี้เรียบร้อยแล้วดังนั้นให้ตรวจสอบชุดประกอบของคุณก่อน

นี่คือข้อมูลเพิ่มเติมเกี่ยวกับการแตกแขนงและเฟ็ล:

http://assemblyrequired.crashworks.org/tag/intrinsics/


float result = (foo> bar) หรือไม่ 2.f: 1.f
knight666

3
@ knight666: นั่นจะยังคงผลิตสาขาที่ไหนก็ได้ถ้าหาก "ทำ" ฉันบอกว่ามันเป็นอย่างนั้นเพราะอย่างน้อยใน ARM, ลำดับเล็ก ๆ อย่างนั้นที่สามารถนำไปใช้ได้กับคำสั่งแบบมีเงื่อนไขซึ่ง don;
chrisbtoo

1
@ knight666 ถ้าคุณโชคดีผู้แปลสามารถเปลี่ยนมันให้กลายเป็น fsel ได้ แต่ก็ไม่แน่นอน FWIW ฉันจะเขียนตัวอย่างข้อมูลนั้นด้วยตัวดำเนินการระดับตติยภูมิและจากนั้นจึงปรับการ fsel ให้เป็นอิสระถ้า profiler ตกลง
tenpn

ใน IA32 คุณได้รับ CMOVcc แทน
Skizz

ดูเพิ่มเติมที่blueraja.com/blog/285/… (โปรดทราบว่าในกรณีนี้หากคอมไพเลอร์ดีควรทำการปรับให้เหมาะสมนี้เองดังนั้นจึงไม่ใช่สิ่งที่คุณต้องกังวลตามปกติ)
BlueRaja - Danny Pflughoeft

16

หลีกเลี่ยงการเข้าถึงหน่วยความจำและสุ่มโดยเฉพาะอย่างยิ่งค่าใช้จ่ายทั้งหมด

นั่นเป็นสิ่งสำคัญที่สุดเพียงอย่างเดียวในการปรับแต่งสำหรับซีพียูสมัยใหม่ คุณสามารถทำเลขคณิตของเลขคณิตและสาขาที่คาดการณ์ผิดจำนวนมากในเวลาที่คุณรอข้อมูลจาก RAM

คุณยังสามารถอ่านกฎนี้ด้วยวิธีอื่น ๆ : ทำการคำนวณให้มากที่สุดเท่าที่จะทำได้ระหว่างการเข้าถึงหน่วยความจำ


13

ใช้ Compiler Intrinsics

ตรวจสอบให้แน่ใจว่าคอมไพเลอร์สร้างแอสเซมบลีที่มีประสิทธิภาพที่สุดสำหรับการดำเนินการบางอย่างโดยใช้อินทริน - สร้างสิ่งที่ดูเหมือนการเรียกใช้ฟังก์ชันที่คอมไพเลอร์เปลี่ยนเป็นแอสเซมบลีที่เหมาะสมที่สุด:

นี่คือข้อมูลอ้างอิงสำหรับ Visual Studioและนี่คือข้อมูลอ้างอิงสำหรับ GCC


11

ลบการเรียกใช้ฟังก์ชันเสมือนที่ไม่จำเป็น

การจัดส่งฟังก์ชั่นเสมือนอาจช้ามาก นี้บทความให้คำอธิบายที่ดีว่าทำไม หากเป็นไปได้สำหรับฟังก์ชั่นที่เรียกว่าหลาย ๆ ครั้งต่อเฟรมให้หลีกเลี่ยง

คุณสามารถทำได้สองวิธี บางครั้งคุณสามารถเขียนคลาสใหม่เพื่อไม่ต้องการรับมรดก - บางทีมันอาจปรากฏว่า MachineGun เป็นคลาสย่อยของ Weapon และคุณสามารถรวมมันเข้าด้วยกันได้

คุณสามารถใช้เท็มเพลตเพื่อแทนที่ polymorphism แบบรันไทม์ด้วย polymorphism แบบรวบรวมเวลา สิ่งนี้จะทำงานได้ก็ต่อเมื่อคุณรู้ชนิดย่อยของออบเจกต์ของคุณตอนรันไทม์และสามารถเขียนซ้ำครั้งใหญ่ได้


9

หลักการพื้นฐานของฉันคือไม่ได้ทำอะไรที่ไม่จำเป็น

หากคุณพบว่าฟังก์ชั่นบางอย่างเป็นคอขวดคุณสามารถปรับฟังก์ชั่นการใช้งานให้เหมาะสมหรือคุณอาจพยายามป้องกันไม่ให้มันถูกเรียกตั้งแต่แรก

นี่ไม่ได้แปลว่าคุณกำลังใช้อัลกอริทึมที่ไม่ดี มันอาจหมายความว่าคุณกำลังเรียกใช้การคำนวณทุกเฟรมที่อาจถูกแคชเป็นระยะเวลาสั้น ๆ (หรือคำนวณล่วงหน้าทั้งหมด)

ฉันมักจะลองวิธีนี้ก่อนที่จะพยายามเพิ่มประสิทธิภาพในระดับต่ำจริงๆ


2
คำถามนี้ถือว่าคุณได้ทำสิ่งที่เป็นโครงสร้างทั้งหมดแล้ว
tenpn

2
มันทำ แต่บ่อยครั้งที่คุณคิดว่าคุณมีและคุณไม่ได้ ดังนั้นทุกครั้งที่ฟังก์ชั่นที่มีราคาแพงจำเป็นต้องปรับให้ดีขึ้นถามตัวเองว่าคุณจำเป็นต้องเรียกใช้ฟังก์ชันนั้นหรือไม่
Rachel Blum

2
... แต่บางครั้งมันอาจเร็วกว่าที่จะทำการคำนวณแม้ว่าคุณจะทิ้งผลลัพธ์ในภายหลังแทนที่จะเป็นสาขา
tenpn

9

ใช้ SIMD (โดย SSE) หากคุณยังไม่ได้ดำเนินการ Gamasutra มีดีบทความเกี่ยวกับเรื่องนี้ คุณสามารถดาวน์โหลดซอร์สโค้ดได้จากไลบรารี่ที่นำเสนอในตอนท้ายของบทความ


6

ลดการพึ่งพาเครือข่ายเพื่อใช้ CPU pipleline ให้ดีขึ้น

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

ตัวอย่าง:

float *data = ...;
int length = ...;

// Slow version
float total = 0.0f;
int i;
for (i=0; i < length; i++)
{
  total += data[i]
}

// Fast version
float total1, total2, total3, total4;
for (i=0; i < length-3; i += 4)
{
  total1 += data[i];
  total2 += data[i+1];
  total3 += data[i+2];
  total4 += data[i+3];
}
for (; i < length; i++)
{
  total += data[i]
}
total += (total1 + total2) + (total3 + total4);

4

อย่ามองข้ามคอมไพเลอร์ของคุณ - หากคุณใช้ gcc บน Intel คุณสามารถเพิ่มประสิทธิภาพได้อย่างง่ายดายโดยเปลี่ยนไปใช้ตัวรวบรวม Intel C / C ++ หากคุณกำลังกำหนดเป้าหมายแพลตฟอร์ม ARM ให้ตรวจสอบคอมไพเลอร์เชิงพาณิชย์ของ ARM หากคุณอยู่บน iPhone แอปเปิ้ลอนุญาตให้ใช้เสียงดังกราวเริ่มต้นด้วย iOS 4.0 SDK

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

คำแนะนำที่ดีที่สุดของฉันคือละเว้นการปรับให้เหมาะสมในระดับต่ำและมุ่งเน้นที่ระดับที่สูงกว่า คอมไพเลอร์และ CPU ไม่สามารถเปลี่ยนอัลกอริทึมของคุณจาก O (n ^ 2) เป็นอัลกอริทึม O (1) ไม่ว่าพวกเขาจะได้รับผลดีเพียงใด นั่นจะทำให้คุณต้องมองสิ่งที่คุณพยายามทำและหาวิธีที่ดีกว่าที่จะทำ ให้คอมไพเลอร์และ CPU กังวลเกี่ยวกับระดับต่ำและคุณมุ่งเน้นไปที่ระดับกลางถึงระดับสูง


ฉันเห็นสิ่งที่คุณพูด แต่มีจุดเมื่อคุณมาถึง O (logN) และคุณจะไม่ได้รับการเปลี่ยนแปลงใด ๆ เพิ่มเติมจากโครงสร้างที่การเพิ่มประสิทธิภาพระดับต่ำสามารถเข้ามาเล่นและได้รับคุณ นั่นคือครึ่งมิลลิวินาทีพิเศษ
tenpn

1
ดูคำตอบของฉันอีกครั้ง: O (บันทึก n) นอกจากนี้หากคุณมองหาครึ่งมิลลิวินาทีคุณอาจต้องดูในระดับที่สูงขึ้น นั่นคือ 3% ของเวลาเฟรมของคุณ!
Rachel Blum

4

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

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

ในทางเทคนิค 'จำกัด ' ไม่มีอยู่ใน C ++ มาตรฐาน แต่มีรายการเทียบเท่าเฉพาะแพลตฟอร์มสำหรับคอมไพเลอร์ C ++ ส่วนใหญ่ดังนั้นจึงควรพิจารณา

ดูเพิ่มเติมที่: http://cellperformance.beyond3d.com/articles/2006/05/demystifying-the-restrict-keyword.html


2

เตรียมทุกอย่าง!

ข้อมูลเพิ่มเติมที่คุณให้คอมไพเลอร์เกี่ยวกับข้อมูลการเพิ่มประสิทธิภาพที่ดีขึ้นคือ (อย่างน้อยก็ในประสบการณ์ของฉัน)

void foo(Bar * x) {...;}

กลายเป็น;

void foo(const Bar * const x) {...;}

คอมไพเลอร์รู้แล้วว่าตัวชี้ x จะไม่เปลี่ยนแปลงและข้อมูลที่ชี้ไปจะไม่เปลี่ยนแปลงเช่นกัน

สิทธิประโยชน์อื่น ๆ เพิ่มเติมคือคุณสามารถลดจำนวนข้อผิดพลาดโดยไม่ได้ตั้งใจหยุดตัวเอง (หรืออื่น ๆ ) แก้ไขสิ่งที่พวกเขาไม่ควร


และรหัสเพื่อนของคุณจะรักคุณ!
tenpn

4
constไม่ปรับปรุงการปรับให้เหมาะสมของคอมไพเลอร์ True คอมไพเลอร์สามารถสร้างโค้ดที่ดีกว่าได้หากรู้ว่าตัวแปรจะไม่เปลี่ยนแปลง แต่constไม่ได้ให้การรับประกันที่เพียงพอ
deft_code

3
Nope 'จำกัด ' มีประโยชน์มากกว่า 'const' ดูgamedev.stackexchange.com/questions/853/…
Justicle

+1 ppl บอกว่าไม่สามารถช่วยเหลือ const ผิด ... infoq.com/presentations/kixeye-scalability
NoSenseEtAl

2

วิธีที่ดีที่สุดในการเพิ่มประสิทธิภาพคือการเปลี่ยนอัลกอริทึมของคุณ การใช้งานทั่วไปที่น้อยกว่ายิ่งคุณเข้าใกล้โลหะมากขึ้นเท่านั้น

สมมติว่าทำไปแล้ว ....

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

หลีกเลี่ยงแคชคิดถึง กระบวนการแบทช์ให้มากที่สุดเท่าที่จะทำได้ หลีกเลี่ยงฟังก์ชั่นเสมือนและทางอ้อมอื่น ๆ

ในที่สุดวัดทุกอย่าง กฎการเปลี่ยนแปลงตลอดเวลา สิ่งที่เคยเพิ่มความเร็วรหัส 3 ปีที่ผ่านมาตอนนี้ช้าลง ตัวอย่างที่ดีคือ 'ใช้ฟังก์ชั่นคณิตศาสตร์คู่แทนรุ่นลอย' ฉันจะไม่ได้ตระหนักถึงสิ่งนั้นหากฉันไม่ได้อ่าน

ฉันลืม - ไม่มีคอนสตรัคค่าเริ่มต้นกำหนดตัวแปรของคุณให้ชัดเจนหรือถ้าคุณยืนยันอย่างน้อยก็สร้างคอนสตรัคเตอร์ที่ไม่มี ระวังสิ่งที่ไม่ปรากฏในโปรไฟล์ เมื่อคุณสูญเสียรอบที่ไม่จำเป็นหนึ่งรอบต่อบรรทัดของโค้ดไม่มีอะไรที่จะปรากฏใน profiler ของคุณ แต่คุณจะสูญเสียวงจรโดยรวมทั้งหมด โปรดทราบอีกครั้งว่ารหัสของคุณกำลังทำอะไรอยู่ ทำให้หน้าที่หลักของคุณผอมเพรียว Foolproof รุ่นสามารถเรียกถ้าจำเป็น แต่ไม่จำเป็นเสมอไป ความคล่องตัวมาพร้อมกับราคา - ประสิทธิภาพเป็นหนึ่งเดียว

แก้ไขเพื่ออธิบายว่าทำไมไม่มีการเริ่มต้นเริ่มต้น: รหัสจำนวนมากพูดว่า: Vector3 bla; bla = DoSomething ();

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

Vector3 bla (noInit); bla = doSomething ();


/ ไม่ / เริ่มสมาชิกของคุณในตัวสร้าง? มันช่วยได้อย่างไร
tenpn

ดูโพสต์ที่แก้ไข ไม่พอดีในช่องแสดงความคิดเห็น
Kaj

const Vector3 = doSomething()? จากนั้นการเพิ่มประสิทธิภาพของค่าตอบแทนสามารถส่งเข้ามาและอาจทำให้เกิดการมอบหมายหรือสองอย่าง
tenpn

1

ลดการประเมินผลนิพจน์แบบบูล

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

if ((foo && bar) || blah) { ... } 

กลายเป็น:

if ((foo & bar) | blah) { ... }

ใช้เลขคณิตเลขจำนวนเต็มแทน หาก foos และ bar ของคุณเป็นค่าคงที่หรือถูกประเมินก่อน if () ค่านี้อาจเร็วกว่าเวอร์ชันบูลีนปกติ

โบนัสเป็นเวอร์ชันทางคณิตศาสตร์ที่มีสาขาน้อยกว่าเวอร์ชันบูลีนทั่วไป ซึ่งเป็นวิธีการอื่นเพิ่มประสิทธิภาพ

ข้อเสียที่ยิ่งใหญ่คือการที่คุณสูญเสียการประเมินผลขี้เกียจ - foo != NULL & foo->dereference()บล็อกทั้งมีการประเมินเพื่อให้คุณไม่สามารถทำ ด้วยเหตุนี้จึงพิสูจน์ได้ว่านี่เป็นเรื่องยากที่จะรักษาและการแลกเปลี่ยนอาจจะดีเกินไป


1
นั่นเป็นการแลกเปลี่ยนที่ไม่สำคัญนักสำหรับการแสดงส่วนใหญ่เพราะมันไม่ชัดเจนในทันทีว่ามันตั้งใจ
บ๊อบซอมเมอร์

ฉันเห็นด้วยกับคุณเกือบทั้งหมด ฉันบอกว่ามันหมดหวัง!
tenpn

3
สิ่งนี้จะไม่ทำลายการลัดวงจรและทำให้การคาดคะเนสาขาไม่น่าเชื่อถือมากขึ้นหรือไม่
Egon

1
ถ้า foo เป็น 2 และ bar เป็น 1 ดังนั้นรหัสจะไม่ทำงานเหมือนเดิม นั่นไม่ใช่ข้อเสียที่ใหญ่ที่สุดที่ฉันคิด

1
โดยปกติแล้วบูลีนใน C ++ นั้นรับประกันว่าจะเป็น 0 หรือ 1 ดังนั้นตราบใดที่คุณเท่านั้นที่ทำสิ่งนี้ด้วย bools ที่คุณปลอดภัย เพิ่มเติม: altdevblogaday.org/2011/04/18/understanding-your-bool-type
tenpn

1

คอยดูการใช้งานสแต็กของคุณ

ทุกสิ่งที่คุณเพิ่มลงในสแต็กเป็นการเพิ่มและสร้างพิเศษเมื่อเรียกใช้ฟังก์ชัน เมื่อจำเป็นต้องใช้พื้นที่สแต็คจำนวนมากในบางครั้งอาจเป็นประโยชน์ในการจัดสรรหน่วยความจำในการทำงานล่วงหน้าและหากแพลตฟอร์มที่คุณใช้งานอยู่มี RAM ที่รวดเร็วสำหรับการใช้งาน - ทั้งหมดนั้นดีกว่า!

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