ในสถานการณ์ที่ประสิทธิภาพมีความสำคัญสูงสุดคอมไพเลอร์ C มักจะไม่สร้างโค้ดที่เร็วที่สุดเมื่อเทียบกับสิ่งที่คุณสามารถทำได้ด้วยภาษาแอสเซมบลีที่ปรับแต่งด้วยมือ ฉันมักจะใช้เส้นทางของการต่อต้านน้อยที่สุด - สำหรับกิจวัตรเล็ก ๆ เช่นนี้ฉันแค่เขียนโค้ด asm และมีความคิดที่ดีว่าจะต้องใช้กี่รอบในการดำเนินการ คุณอาจสามารถใช้รหัส C และรับคอมไพเลอร์เพื่อสร้างผลลัพธ์ที่ดีได้ แต่คุณอาจเสียเวลาในการปรับแต่งผลลัพธ์ด้วยวิธีนี้ คอมไพเลอร์ (โดยเฉพาะจาก Microsoft) มาไกลในช่วงไม่กี่ปีที่ผ่านมา แต่คอมไพเลอร์ยังไม่ฉลาดเท่าคอมไพเลอร์ระหว่างหูของคุณเพราะคุณกำลังทำงานกับสถานการณ์เฉพาะของคุณไม่ใช่แค่กรณีทั่วไป คอมไพเลอร์ไม่สามารถใช้ประโยชน์จากคำสั่งบางอย่าง (เช่น LDM) ที่สามารถเร่งความเร็วได้และมัน ไม่น่าจะฉลาดพอที่จะคลายการวนซ้ำ นี่คือวิธีการทำซึ่งรวม 3 แนวคิดที่ฉันพูดถึงในความคิดเห็นของฉัน: Loop unrolling, cache prefetch และใช้ประโยชน์จากคำสั่ง multiple load (ldm) จำนวนรอบคำสั่งจะอยู่ที่ประมาณ 3 นาฬิกาต่อองค์ประกอบอาร์เรย์ แต่สิ่งนี้ไม่ได้คำนึงถึงความล่าช้าของหน่วยความจำ
ทฤษฎีการทำงาน:การออกแบบ CPU ของ ARM ดำเนินการคำสั่งส่วนใหญ่ในหนึ่งรอบนาฬิกา แต่คำสั่งจะดำเนินการในท่อ คอมไพเลอร์ C จะพยายามกำจัดความล่าช้าของท่อโดยการแทรกคำสั่งอื่น ๆ ไว้ระหว่างนั้น เมื่อนำเสนอด้วยการวนซ้ำที่แน่นหนาเช่นรหัส C ดั้งเดิมคอมไพเลอร์จะมีช่วงเวลาที่ยากลำบากในการซ่อนความล่าช้าเนื่องจากค่าที่อ่านจากหน่วยความจำจะต้องถูกเปรียบเทียบทันที รหัสของฉันด้านล่างสลับระหว่าง 2 ชุดจาก 4 รีจิสเตอร์เพื่อลดความล่าช้าของหน่วยความจำเองและไปป์ไลน์ที่ดึงข้อมูล โดยทั่วไปเมื่อทำงานกับชุดข้อมูลขนาดใหญ่และรหัสของคุณไม่ได้ใช้ประโยชน์จากการลงทะเบียนส่วนใหญ่หรือทั้งหมดที่มีอยู่คุณจะไม่ได้รับประสิทธิภาพสูงสุด
; r0 = count, r1 = source ptr, r2 = comparison value
stmfd sp!,{r4-r11} ; save non-volatile registers
mov r3,r0,LSR #3 ; loop count = total count / 8
pld [r1,#128]
ldmia r1!,{r4-r7} ; pre load first set
loop_top:
pld [r1,#128]
ldmia r1!,{r8-r11} ; pre load second set
cmp r4,r2 ; search for match
cmpne r5,r2 ; use conditional execution to avoid extra branch instructions
cmpne r6,r2
cmpne r7,r2
beq found_it
ldmia r1!,{r4-r7} ; use 2 sets of registers to hide load delays
cmp r8,r2
cmpne r9,r2
cmpne r10,r2
cmpne r11,r2
beq found_it
subs r3,r3,#1 ; decrement loop count
bne loop_top
mov r0,#0 ; return value = false (not found)
ldmia sp!,{r4-r11} ; restore non-volatile registers
bx lr ; return
found_it:
mov r0,#1 ; return true
ldmia sp!,{r4-r11}
bx lr
อัปเดต:
มีผู้สงสัยมากมายในความคิดเห็นที่คิดว่าประสบการณ์ของฉันเป็นเรื่องเล็กน้อย / ไร้ค่าและต้องการการพิสูจน์ ฉันใช้ GCC 4.8 (จาก Android NDK 9C) เพื่อสร้างผลลัพธ์ต่อไปนี้ด้วยการเพิ่มประสิทธิภาพ -O2 (การเพิ่มประสิทธิภาพทั้งหมดเปิดอยู่รวมถึงการคลายการวนซ้ำ ) ฉันรวบรวมรหัส C ดั้งเดิมที่นำเสนอในคำถามด้านบน นี่คือสิ่งที่ GCC ผลิต:
.L9: cmp r3, r0
beq .L8
.L3: ldr r2, [r3, #4]!
cmp r2, r1
bne .L9
mov r0, #1
.L2: add sp, sp, #1024
bx lr
.L8: mov r0, #0
b .L2
เอาต์พุตของ GCC ไม่เพียง แต่ไม่คลายการวนซ้ำ แต่ยังทำให้สิ้นเปลืองนาฬิกาบนแผงลอยหลังจาก LDR ต้องมีอย่างน้อย 8 นาฬิกาต่อองค์ประกอบอาร์เรย์ มันทำงานได้ดีในการใช้ที่อยู่เพื่อทราบว่าเมื่อใดที่จะออกจากลูป แต่สิ่งมหัศจรรย์ทั้งหมดที่คอมไพเลอร์สามารถทำได้นั้นไม่มีที่ไหนที่จะพบได้ในโค้ดนี้ ฉันไม่ได้รันโค้ดบนแพลตฟอร์มเป้าหมาย (ฉันไม่ได้เป็นเจ้าของรหัส) แต่ใครก็ตามที่มีประสบการณ์ในการทำงานของโค้ด ARM จะเห็นว่าโค้ดของฉันเร็วขึ้น
อัปเดต 2:
ฉันให้โอกาส Visual Studio 2013 SP2 ของ Microsoft ในการทำโค้ดให้ดีขึ้น มันสามารถใช้คำแนะนำ NEON เพื่อกำหนดค่าเริ่มต้นอาร์เรย์ของฉันเป็นเวกเตอร์ได้ แต่การค้นหาค่าเชิงเส้นที่เขียนโดย OP นั้นคล้ายกับสิ่งที่ GCC สร้างขึ้น (ฉันเปลี่ยนชื่อป้ายกำกับเพื่อให้อ่านได้ง่ายขึ้น):
loop_top:
ldr r3,[r1],#4
cmp r3,r2
beq true_exit
subs r0,r0,#1
bne loop_top
false_exit: xxx
bx lr
true_exit: xxx
bx lr
อย่างที่บอกไปว่าฉันไม่ได้เป็นเจ้าของฮาร์ดแวร์ที่แน่นอนของ OP แต่ฉันจะทดสอบประสิทธิภาพของ nVidia Tegra 3 และ Tegra 4 จาก 3 เวอร์ชันที่แตกต่างกันและโพสต์ผลลัพธ์ที่นี่เร็ว ๆ นี้
อัปเดต 3:
ฉันรันโค้ดและรหัส ARM ที่คอมไพล์ของ Microsoft บน Tegra 3 และ Tegra 4 (Surface RT, Surface RT 2) ฉันวิ่งวนซ้ำ 1000000 ครั้งซึ่งหาคู่ไม่ได้เพื่อให้ทุกอย่างอยู่ในแคชและวัดได้ง่าย
My Code MS Code
Surface RT 297ns 562ns
Surface RT 2 172ns 296ns
ในทั้งสองกรณีรหัสของฉันทำงานเร็วขึ้นเกือบสองเท่า ซีพียู ARM สมัยใหม่ส่วนใหญ่อาจให้ผลลัพธ์ที่คล้ายกัน