ฉันสังเกตเห็นครั้งแรกในปี 2009 ว่า GCC (อย่างน้อยในโครงการของฉันและในเครื่องของฉัน) มีแนวโน้มที่จะสร้างรหัสที่เร็วขึ้นอย่างเห็นได้ชัดถ้าฉันปรับขนาด ( -Os) แทนความเร็ว ( -O2หรือ-O3) และฉันสงสัยตั้งแต่นั้นมา
ฉันมีการจัดการเพื่อสร้างรหัส (ค่อนข้างโง่) ที่แสดงพฤติกรรมที่น่าแปลกใจนี้และมีขนาดเล็กพอที่จะโพสต์ที่นี่
const int LOOP_BOUND = 200000000;
__attribute__((noinline))
static int add(const int& x, const int& y) {
return x + y;
}
__attribute__((noinline))
static int work(int xval, int yval) {
int sum(0);
for (int i=0; i<LOOP_BOUND; ++i) {
int x(xval+sum);
int y(yval+sum);
int z = add(x, y);
sum += z;
}
return sum;
}
int main(int , char* argv[]) {
int result = work(*argv[1], *argv[2]);
return result;
}
ถ้าผมรวบรวมไว้ด้วย-Osก็จะใช้เวลา 0.38 วินาทีในการรันโปรแกรมนี้และ 0.44 s ถ้ามันจะรวบรวมกับหรือ-O2 -O3เวลาเหล่านี้ได้รับอย่างสม่ำเสมอและไม่มีเสียงรบกวน (gcc 4.7.2, x86_64 GNU / Linux, Intel Core i5-3320M)
(อัปเดต: ฉันย้ายรหัสแอสเซมบลีทั้งหมดไปที่GitHub : พวกเขาทำให้โพสต์ป่องและเห็นได้ชัดว่าเพิ่มมูลค่าน้อยมากสำหรับคำถามเนื่องจากfno-align-*ค่าสถานะมีผลเหมือนกัน)
ที่นี่เป็นที่ที่สร้างขึ้นประกอบกับและ-Os-O2
แต่น่าเสียดายที่ความเข้าใจของฉันของการชุมนุมถูก จำกัด มากดังนั้นฉันมีความคิดว่าสิ่งที่ผมทำต่อไปได้ถูกต้องไม่มี: ฉันคว้าประกอบ-O2และผสานความแตกต่างทั้งหมดไปสู่การชุมนุมสำหรับการ-Os ยกเว้น.p2alignเส้นส่งผลให้ที่นี่ รหัสนี้ยังคงทำงานใน 0.38s และสิ่งที่แตกต่างคือ .p2align สิ่งที่
หากฉันเดาถูกต้องสิ่งเหล่านี้เป็นช่องว่างสำหรับการจัดเรียงสแต็ค ตามที่เหตุใด GCC pad จึงทำงานกับ NOP มันทำด้วยหวังว่ารหัสจะทำงานได้เร็วขึ้น แต่เห็นได้ชัดว่าการเพิ่มประสิทธิภาพนี้ backfired ในกรณีของฉัน
มันเป็นช่องว่างภายในที่เป็นผู้กระทำผิดในกรณีนี้หรือไม่? ทำไมและอย่างไร
เสียงรบกวนที่ทำให้สวยทำให้การปรับตั้งเวลาแบบไมโครเป็นไปไม่ได้
ฉันจะแน่ใจได้อย่างไรว่าการจัดเรียง Lucky / Unlucky โดยบังเอิญไม่รบกวนเมื่อฉันทำการปรับให้เหมาะสมแบบไมโคร (ไม่เกี่ยวข้องกับการจัดเรียงสแต็ค) ในซอร์สโค้ด C หรือ C ++
UPDATE:
การทำตามคำตอบของ Pascal Cuoqฉันได้นิดหน่อยกับการจัดแนวเล็กน้อย เมื่อผ่าน-O2 -fno-align-functions -fno-align-loopsไปยัง gcc ทั้งหมด.p2alignจะหายไปจากชุดประกอบและชุดประมวลผลที่สร้างขึ้นจะทำงานใน 0.38 วินาที ตามเอกสาร gcc :
-Os เปิดใช้งานการปรับให้เหมาะสม -O2 ทั้งหมด [แต่] -Os ปิดใช้งานแฟล็กการปรับให้เหมาะสมต่อไปนี้:
-falign-functions -falign-jumps -falign-loops -falign-labels -freorder-blocks -freorder-blocks-and-partition -fprefetch-loop-arrays
ดังนั้นดูเหมือนว่าปัญหาการจัดตำแหน่งผิดพลาด
ฉันยังคงสงสัยเกี่ยวกับการ-march=nativeตามข้อเสนอแนะในคำตอบของ Marat Dukhan ฉันไม่เชื่อว่ามันไม่ได้เป็นเพียงแค่การแทรกแซงปัญหาการจัดตำแหน่ง (ผิด) นี้; มันไม่มีผลกับเครื่องของฉันเลย (ถึงกระนั้นฉัน upvoted คำตอบของเขา)
อัปเดต 2:
เราสามารถนำ-Osออกมาจากภาพ เวลาต่อไปนี้ได้มาจากการคอมไพล์ด้วย
-O2 -fno-omit-frame-pointer0.37s-O2 -fno-align-functions -fno-align-loops0.37s-S -O2จากนั้นย้ายชุดประกอบด้วยตนเองadd()หลังจากwork()0.37 วินาที-O20.44s
ดูเหมือนว่าระยะทางadd()จากไซต์การโทรของฉันมีความสำคัญมาก ฉันได้พยายามperfแต่การส่งออกของperf statและperf reportทำให้รู้สึกน้อยมากกับผม อย่างไรก็ตามฉันสามารถรับผลลัพธ์ที่สอดคล้องกันเพียงรายการเดียวเท่านั้น:
-O2:
602,312,864 stalled-cycles-frontend # 0.00% frontend cycles idle
3,318 cache-misses
0.432703993 seconds time elapsed
[...]
81.23% a.out a.out [.] work(int, int)
18.50% a.out a.out [.] add(int const&, int const&) [clone .isra.0]
[...]
¦ __attribute__((noinline))
¦ static int add(const int& x, const int& y) {
¦ return x + y;
100.00 ¦ lea (%rdi,%rsi,1),%eax
¦ }
¦ ? retq
[...]
¦ int z = add(x, y);
1.93 ¦ ? callq add(int const&, int const&) [clone .isra.0]
¦ sum += z;
79.79 ¦ add %eax,%ebx
สำหรับfno-align-*:
604,072,552 stalled-cycles-frontend # 0.00% frontend cycles idle
9,508 cache-misses
0.375681928 seconds time elapsed
[...]
82.58% a.out a.out [.] work(int, int)
16.83% a.out a.out [.] add(int const&, int const&) [clone .isra.0]
[...]
¦ __attribute__((noinline))
¦ static int add(const int& x, const int& y) {
¦ return x + y;
51.59 ¦ lea (%rdi,%rsi,1),%eax
¦ }
[...]
¦ __attribute__((noinline))
¦ static int work(int xval, int yval) {
¦ int sum(0);
¦ for (int i=0; i<LOOP_BOUND; ++i) {
¦ int x(xval+sum);
8.20 ¦ lea 0x0(%r13,%rbx,1),%edi
¦ int y(yval+sum);
¦ int z = add(x, y);
35.34 ¦ ? callq add(int const&, int const&) [clone .isra.0]
¦ sum += z;
39.48 ¦ add %eax,%ebx
¦ }
สำหรับ-fno-omit-frame-pointer:
404,625,639 stalled-cycles-frontend # 0.00% frontend cycles idle
10,514 cache-misses
0.375445137 seconds time elapsed
[...]
75.35% a.out a.out [.] add(int const&, int const&) [clone .isra.0] ¦
24.46% a.out a.out [.] work(int, int)
[...]
¦ __attribute__((noinline))
¦ static int add(const int& x, const int& y) {
18.67 ¦ push %rbp
¦ return x + y;
18.49 ¦ lea (%rdi,%rsi,1),%eax
¦ const int LOOP_BOUND = 200000000;
¦
¦ __attribute__((noinline))
¦ static int add(const int& x, const int& y) {
¦ mov %rsp,%rbp
¦ return x + y;
¦ }
12.71 ¦ pop %rbp
¦ ? retq
[...]
¦ int z = add(x, y);
¦ ? callq add(int const&, int const&) [clone .isra.0]
¦ sum += z;
29.83 ¦ add %eax,%ebx
ดูเหมือนว่าเราจะหยุดการโทรไปadd()ในกรณีที่ช้า
ฉันได้ตรวจสอบทุกสิ่งที่perf -eสามารถพ่นออกมาในเครื่องของฉัน; ไม่ใช่แค่สถิติที่ให้ไว้ข้างต้น
สำหรับปฏิบัติการที่เหมือนกันการstalled-cycles-frontendแสดงความสัมพันธ์เชิงเส้นกับเวลาดำเนินการ ฉันไม่ได้สังเกตเห็นสิ่งอื่นใดที่จะมีความสัมพันธ์อย่างชัดเจน (การเปรียบเทียบstalled-cycles-frontendสำหรับไฟล์เรียกทำงานที่แตกต่างกันนั้นไม่สมเหตุสมผลสำหรับฉัน)
ฉันรวมแคชที่หายไปเนื่องจากเป็นความคิดเห็นแรก ฉันตรวจสอบการพลาดแคชทั้งหมดที่สามารถวัดได้บนเครื่องของฉันด้วยperfไม่ใช่เฉพาะที่ระบุไว้ด้านบน แคชคิดถึงมากและมีเสียงดังมากและไม่มีความสัมพันธ์กับเวลาดำเนินการ