การเพิ่มประสิทธิภาพเบื้องต้น
เมื่อพูดถึงการเพิ่มประสิทธิภาพ หลายๆ คนไม่สนใจว่า "ความเร็วของคอมพิวเตอร์ตอนนี้เร็วมาก แล้วอะไรจะเร็วขึ้นอีก 2-3 เปอร์เซ็นต์ล่ะ" สิ่งนี้สมเหตุสมผลดี ผลลัพธ์ที่รวบรวมโดยคอมไพเลอร์ปัจจุบันได้รับการปรับให้เหมาะสมที่สุด ยกเว้นการพัฒนาซอฟต์แวร์เฉพาะ เช่น กราฟิก รูปภาพ และมัลติมีเดีย การเพิ่มประสิทธิภาพโดยเจตนาไม่จำเป็นในกรณีส่วนใหญ่ แต่ถ้านักพัฒนากำลังเขียนโค้ดในขณะนั้น คุณได้ตระหนักถึงการเพิ่มประสิทธิภาพแล้ว ในขณะที่ทำการเพิ่มประสิทธิภาพ คุณสามารถมั่นใจหรือแม้กระทั่งปรับปรุงประสิทธิภาพการพัฒนาได้
แน่นอนว่า การออกแบบอัลกอริธึมถือเป็นหัวใจสำคัญของการเพิ่มประสิทธิภาพ ในกรณีส่วนใหญ่ ประสิทธิภาพการทำงานของโปรแกรมจะขึ้นอยู่กับความเข้าใจโดยรวมของนักพัฒนาซอฟต์แวร์ การออกแบบอัลกอริธึม ฯลฯ ! แต่บางครั้งการปรับรายละเอียดให้เหมาะสมก็สมเหตุสมผลเช่นกัน!
นอกจากนี้ ในหลายกรณี การเพิ่มประสิทธิภาพประเภทนี้ไม่จำเป็นต้องเขียนโค้ดโดยตรงผ่านแอสเซมบลี แต่ในกรณีนี้ ยังสามารถสะท้อนถึงความเหนือกว่าของการเรียนรู้ความรู้แอสเซมบลีได้!
เช่นสองฟังก์ชันต่อไปนี้:
ฟังก์ชั่น GetBit (i: พระคาร์ดินัล; n: พระคาร์ดินัล): บูลีน;
เริ่ม
ผลลัพธ์ := Boolean((i shr n) และ 1);
จบ;
ฟังก์ชั่น GetBit (i: พระคาร์ดินัล; n: พระคาร์ดินัล): บูลีน;
เริ่ม
ผลลัพธ์ := Boolean((1 shl n) และ i);
จบ;
รหัสการประกอบที่สอดคล้องกัน:
MOV ECX, EDX
SHR EAX, ซีแอล
และ EAX, $01
MOV ECX, EDX
MOV EDX, $01
เอสเอชแอล EDX, ซีแอล
และ EAX, EDX
พวกมันมีฟังก์ชันเหมือนกัน โดยทั้งหมดรับค่าของบิตที่แน่นอนของ i ส่งกลับค่า True หากเป็น 1 และคืนค่าเป็น False หากเป็น 0!
ภายนอก คุณอาจคิดว่าประสิทธิภาพในการดำเนินการของทั้งสองฟังก์ชันเท่ากัน แต่ในความเป็นจริงแล้ว มีความแตกต่างกัน ในเวลานี้ ค่าจะถูกเก็บไว้ในรีจิสเตอร์ EAX และการดำเนินการกะสามารถดำเนินการได้โดยตรง แต่โปรแกรมที่สองจะแตกต่างออกไป เพื่อให้การดำเนินการกะในค่าทันที 1 เสร็จสมบูรณ์ จะต้องโอนไปยังรีจิสเตอร์ก่อน ดังนั้นต้องมีอีกหนึ่งคำสั่ง! แน่นอนว่าไม่ใช่ในทุกกรณี คำสั่งที่น้อยกว่าจะต้องเร็วกว่าคำสั่งที่มากขึ้น ในระหว่างการดำเนินการเฉพาะ เราต้องพิจารณาประเด็นต่างๆ เช่น วงจรสัญญาณนาฬิกาของการดำเนินการคำสั่ง และการจับคู่คำสั่ง (เพิ่มเติมเกี่ยวกับเรื่องนี้ในภายหลัง) ปัญหาอย่างอิสระเฉพาะเมื่อสามารถทำการเปรียบเทียบได้เฉพาะในสภาพแวดล้อมโค้ดเฉพาะเท่านั้น
ภายใต้สถานการณ์ปกติ ความแตกต่างของประสิทธิภาพนี้มีน้อยมาก แต่ก็ไม่ใช่เรื่องเลวร้ายที่จะตระหนักถึงการปรับให้เหมาะสมระหว่างการเขียนโปรแกรม! หากโค้ดดังกล่าวอยู่ในเลเยอร์ด้านในสุดของลูป และรอบสัญญาณนาฬิกา N สะสมผ่านลูปจำนวนมาก ความแตกต่างในประสิทธิภาพการดำเนินการอาจมีขนาดใหญ่มาก!
ข้างต้นเป็นเพียงตัวอย่างเล็กๆ น้อยๆ จะเห็นได้ว่าหากคุณสามารถคิดถึงปัญหาบางอย่างจากมุมมองของ Assembly ในระหว่างการพัฒนา คุณจะสามารถเขียนโค้ดที่มีรายละเอียดมีประสิทธิภาพมากขึ้นในภาษาระดับสูง ในขณะเดียวกันก็มั่นใจได้ถึงประสิทธิภาพในการพัฒนา! แต่ยังคงมีหลายครั้งที่ต้องเพิ่มประสิทธิภาพโดยละเอียดให้เสร็จสิ้นโดยใช้โค้ดแอสเซมบลีแบบฝัง และบางครั้งเนื่องจากการใช้โค้ดแอสเซมบลีแบบฝัง การเขียนโค้ดจึงมีประสิทธิภาพมากขึ้นเช่นกัน
หากคุณต้องการกลับลำดับไบต์ของตัวเลข 32 หลัก คุณจะดำเนินการดังกล่าวในภาษาระดับสูงใน Delphi ได้อย่างไร คุณสามารถใช้การเลื่อน คุณยังสามารถเรียกใช้ฟังก์ชัน Swap ในตัวได้หลายครั้ง แต่ถ้าคุณนึกถึงคำสั่ง BSWAP ทุกอย่างจะง่ายมาก
ฟังก์ชั่น SwapLong(ค่า: พระคาร์ดินัล): พระคาร์ดินัล;
asm
BSWAP EAX
จบ;
หมายเหตุ: เช่นเดียวกับข้างต้น ค่าของ Value จะถูกจัดเก็บไว้ใน EAX ของรีจิสเตอร์ และค่า 32 หลักก็จะถูกส่งกลับผ่าน EAX เช่นกัน ดังนั้นจึงจำเป็นต้องใช้เพียงประโยคเดียวเท่านั้น
แน่นอนว่าการเพิ่มประสิทธิภาพแอสเซมบลีแบบฝังส่วนใหญ่นั้นไม่ใช่เรื่องง่าย แต่เป็นการยากที่จะบรรลุการเพิ่มประสิทธิภาพในเชิงลึกมากขึ้นด้วยความรู้ด้านแอสเซมบลีเพียงเล็กน้อยที่เรียนรู้ในวิทยาลัยเท่านั้น ประสบการณ์จะได้รับจากการสะสมอย่างต่อเนื่องและการเปรียบเทียบรหัสแอสเซมบลีที่คอมไพล์แล้ว! โชคดีที่ในกรณีส่วนใหญ่ การเพิ่มประสิทธิภาพโดยละเอียดไม่ใช่ส่วนหลักของการออกแบบโปรแกรม
อย่างไรก็ตาม หากโปรแกรมที่พัฒนาขึ้นเกี่ยวข้องกับกราฟิก รูปภาพ มัลติมีเดีย ฯลฯ ยังคงจำเป็นต้องดำเนินการปรับให้เหมาะสมในเชิงลึกมากขึ้น! โชคดีที่ Delphi6 สามารถให้การสนับสนุนที่ดีได้ ไม่ว่าจะเป็นการปรับคำสั่งจุดลอยตัวให้เหมาะสม หรือการใช้งาน MMX, SSE, 3DNow ฯลฯ แม้ว่าคุณต้องการให้ Delphi เวอร์ชันก่อนหน้ารองรับชุดคำสั่งเพิ่มเติมของ CPU เหล่านี้ หรือต้องการรองรับชุดคำสั่ง CPU ใหม่ในอนาคต คุณสามารถใช้คำสั่งประกอบสี่คำสั่งของ DB, DW, DD และ DQ ที่ Delphi รองรับในชุดประกอบแบบฝัง ( ในคู่มือภาษา Delphi6 อย่างเป็นทางการของ Borland คู่มือภาษาบอกว่ารองรับ DB, DW และ DD เท่านั้น) และการแสดงตัวเลขของคำแนะนำที่เกี่ยวข้องก็สามารถนำมาใช้ได้อย่างยืดหยุ่นเช่นกัน
ชอบ:
DW $A20F //CPUID
DW $770F //EMMS
DB $0F, $6F, $C1 //MOVQ MM0, MM1
การทำความเข้าใจคำแนะนำเป็นเพียงพื้นฐานเท่านั้น หลังจากออกแบบอัลกอริทึมเกี่ยวกับ FPU, MMX และ SSE แล้ว หากคุณต้องการเพิ่มประสิทธิภาพเพิ่มเติม คุณต้องเข้าใจคุณลักษณะทางเทคนิคบางประการของ CPU ด้วย
มาดูโค้ดสองชิ้นต่อไปนี้:
asm
เพิ่ม [a], ECX
เพิ่ม [b], EDX
จบ
asm
MOV EAX, [a]
MOV EBX, [ข]
เพิ่ม EAX, ECX
เพิ่ม EBX, EDX
MOV [a], EAX
MOV [b], EBX
จบ
อันที่สองมีประสิทธิภาพมากกว่าหรือไม่? ผิดดังที่กล่าวข้างต้น คำสั่งที่น้อยลงไม่ได้หมายความว่าประสิทธิภาพในการดำเนินการสูง ตามข้อมูลที่เกี่ยวข้อง วงจรนาฬิกาสำหรับการดำเนินการของคำสั่งทั้งสองในส่วนแรกของโค้ดคือ 3 (แต่ละคำสั่งจำเป็นต้องอ่านสามขั้นตอนให้เสร็จสิ้น การแก้ไขและการเขียนขั้นตอน) รอบสัญญาณนาฬิกาที่ดำเนินการโดยหกคำสั่งในส่วนที่สองของโค้ดคือ 1 ทั้งหมด ดังนั้นโค้ดทั้งสองชิ้นจึงมีประสิทธิภาพเท่ากันใช่ไหม ผิดอีกแล้ว โค้ดชิ้นที่สองทำงานได้มีประสิทธิภาพมากกว่าโค้ดชิ้นแรกจริงๆ! ทำไม เนื่องจาก CPU หลังจากคลาส Pentium มีสองไปป์ไลน์สำหรับดำเนินการคำสั่ง เมื่อสองคำสั่งที่อยู่ติดกันสามารถจับคู่ได้ ก็สามารถดำเนินการได้ในเวลาเดียวกัน! เฉพาะโค้ดสองส่วนข้างต้น มีเหตุผลเฉพาะเจาะจงอะไรบ้าง
แม้ว่าทั้งสองคำสั่งในโค้ดแรกจะสามารถจับคู่กันได้ แต่รอบนาฬิกาการดำเนินการทั้งหมดที่ต้องการคือ 5 แทนที่จะเป็น 3 ในขณะที่คำสั่งทั้งหกในโค้ดที่สองสามารถดำเนินการพร้อมกันได้ ซึ่งนำไปสู่ผลลัพธ์นี้
ทั้งหมดนี้เป็นตัวอย่างง่ายๆ ที่ไม่สามารถช่วยคุณได้มากนัก หากคุณต้องการเพิ่มประสิทธิภาพโปรแกรมเฉพาะเจาะจงจริงๆ คุณควรมองหาบทความพิเศษเกี่ยวกับการเพิ่มประสิทธิภาพ FPU และ MMX หรือค้นหาคู่มือทางเทคนิคเพื่อศึกษาและศึกษาเทคโนโลยี เช่น "การดำเนินการที่ไม่อยู่ในลำดับ" และ "การทำนายสาขา" ฉันแค่หวังว่าเพื่อน ๆ ทุกคนที่อยู่ในวิทยาลัยจะไม่มุ่งความสนใจไปที่เครื่องมือการพัฒนา "สร้างรายได้" และเทคโนโลยีใหม่ ๆ ที่ทันสมัย แต่ยังสามารถใช้เวลามากขึ้นในการวางรากฐานที่มั่นคง คุณสามารถเชี่ยวชาญความรู้ใหม่ ๆ ได้อย่างรวดเร็ว , ด้วยวิธีนี้เท่านั้นที่เราจะเชี่ยวชาญเครื่องมือและทักษะการพัฒนาใหม่ ๆ ได้ในเวลาอันรวดเร็วยิ่งขึ้น... (ละคำนับพันคำ)
แต่ขอย้ำอีกครั้งว่าความรู้ยังคงต้องใช้เพื่อแก้ไขปัญหาเชิงปฏิบัติ หากคุณมุ่งเน้นแต่รายละเอียดด้านเทคนิคทุกวัน คุณอาจกลายเป็นแฮ็กเกอร์ที่เก่งกาจ แต่คุณจะไม่มีวันพัฒนาซอฟต์แวร์ชั้นหนึ่งได้ ดังนั้นจุดประสงค์พื้นฐานจึงต้องยังคงเป็นการสร้างมูลค่า ดังนั้น... ฉันจะไม่พูดถึงมันอีกต่อไป มันจะดูไม่เหมือนบทความด้านเทคนิคจริงๆ ถ้าฉันพูดถึงมันอีกต่อไป -
เอกสารแนบ: นอกเหนือจากการพิจารณาประสิทธิภาพในการดำเนินการแล้ว การเพิ่มประสิทธิภาพโปรแกรมยังต้องคำนึงถึงปัญหาเกี่ยวกับขนาดด้วย (ขนาดเล็กสามารถโหลดหน่วยความจำได้เร็วขึ้นและทำการถอดรหัสคำสั่งและงานอื่น ๆ ได้เร็วขึ้น) ตัวอย่างเช่น หากต้องการล้างการลงทะเบียน EAX ให้ใช้ SUB EAX, EAX หรือ XOR EAX , EAX แทน MOV EAX, $0 แม้ว่ารอบสัญญาณนาฬิกาดำเนินการจะเป็น 1 ทั้งคู่ แต่ความยาวของคำสั่งของคำสั่งแรก (2 ไบต์) นั้นสั้นกว่าของรอบสัญญาณหลัง (5 ไบต์) อย่างเห็นได้ชัด แต่เนื่องจากที่กล่าวมาข้างต้นเป็นเพียงรายละเอียดทั้งหมด จึงไม่ได้กล่าวถึงประเด็นปริมาณ ควรปล่อยให้ปัญหาการลดขนาดเพิ่มเติมเป็นหน้าที่ของคอมไพเลอร์เพื่อแก้ไข เพียงให้ความสนใจเล็กน้อยเมื่อเขียนโค้ด ASM ที่ฝังไว้