อย่าล้อเลียนตัวทำนายสาขาที่แสนสุข
- ช่วงนี้กำลังเขียนแอสเซมบลี AArch64 อยู่มาก
- ไอเดีย "ฉลาด" ที่พยายามลบคำสั่งกระโดดหนึ่งตัวออกจากลูปกลับทำให้ประสิทธิภาพแย่ลง
- อธิบายความผิดพลาดนี้เพื่อไม่ให้คนอื่นพลาดแบบเดียวกัน
ตัวอย่างโค้ด
float run(const float* data, size_t n) {
float g = 0.0;
while (n) {
n--;
const float f = *data++;
foo(f, &g);
}
return g;
}
static void foo(float f, float* g) {
// g를 수정하는 작업
}
แปลเป็นแอสเซมบลี AArch64
// x0: const float* data
// x1: size_t n
// s0: 반환할 float
stp x29, x30, [sp, #-16]!
mov s0, #0.0
loop:
cmp x1, #0
b.eq exit
sub x1, x1, #1
ldr s1, [x0], #4
bl foo
b loop
foo:
// s1에서 읽고 s0에 누적
// ...
ret
exit:
ldp x29, x30, [sp], #16
ret
ความพยายามในการปรับแต่ง
- พยายามลดคำสั่ง
bl เพื่อเพิ่มประสิทธิภาพ
- แต่กลับทำให้ประสิทธิภาพแย่ลงแทน
เปรียบเทียบประสิทธิภาพ
- โค้ดต้นฉบับ: 969 ns
- โค้ดที่ปรับแต่ง: 3.85 µs
วิเคราะห์สาเหตุ
- ตัวทำนายสาขาสับสนเพราะคู่
bl และ ret ไม่ตรงกัน
- ตามเอกสารของ ARM คำสั่ง
ret ช่วยให้คาดเดาการคืนค่าจากฟังก์ชันได้ดีขึ้น
วิธีแก้
- ใช้
br x30 แทน ret
- ประสิทธิภาพกลับมา: 913 ns
การปรับแต่งเพิ่มเติม
- อินไลน์
foo เพื่อเพิ่มประสิทธิภาพ
- ใช้การ unroll ลูปและคำสั่ง SIMD
ประสิทธิภาพสุดท้าย
- SIMD + unroll ลูปด้วยมือ: 94 ns
บทสรุป
- อย่าทำให้ตัวทำนายสาขาสับสน
- โค้ด SIMD เร็วกว่า แต่ผลลัพธ์อาจต่างกันได้เพราะการบวกเลขทศนิยมไม่เป็นไปตามสมบัติการเปลี่ยนหมู่
ความเห็นของ GN⁺
- บทความนี้แสดงให้เห็นความสำคัญของการปรับแต่งแอสเซมบลี AArch64 ได้อย่างดี
- การเข้าใจหลักการทำงานของตัวทำนายสาขาเป็นสิ่งจำเป็นต่อการปรับแต่งประสิทธิภาพ
- การปรับแต่งด้วยคำสั่ง SIMD มีประสิทธิภาพมาก แต่ต้องคำนึงถึงปัญหาเรื่องความแม่นยำ
- หากใช้ภาษาในระดับสูงอย่าง Rust ก็สามารถเพิ่มประสิทธิภาพได้ง่ายผ่านการปรับแต่งของคอมไพเลอร์
- โปรเจ็กต์ที่มีลักษณะใกล้เคียงกัน ได้แก่ คู่มือการปรับแต่งแอสเซมบลีของ Agner Fog
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
มีการสรุปบทความร่วมกับเพื่อน ๆ จากยุค Apple II
Raymond Chen เคยพูดถึงหัวข้อเดียวกันนี้เมื่อเกือบ 20 ปีก่อน
ในโค้ด SIMD สามารถบวกผลรวมในลำดับที่ต่างออกไปได้ เพราะการบวกเลขทศนิยมลอยตัวไม่เป็นไปตามสมบัติการเปลี่ยนหมู่
ตั้งแต่ Rust 1.78 เป็นต้นมา คอมไพเลอร์ใช้การ unroll ลูปที่ดุดันขึ้นและมี SIMD อยู่บ้าง
มีความสับสนว่า x0 เพิ่มค่าอย่างไรในแอสเซมบลี ARM/ARM64
ldr s1, [x0], #4จะโหลดข้อมูลพร้อมเพิ่มค่า x0 ขึ้น 4น่าแปลกใจที่ไม่ได้ลองวิธีที่ซับซ้อนน้อยกว่านี้เพื่อปรับแต่งโค้ดแอสเซมบลี
มีความเห็นว่าอยากให้ผู้เขียนเลิกเปลี่ยนหน่วยไปมาอยู่ตลอด