ฉันสังเกตเห็นครั้งแรกในปี 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-pointer
0.37s-O2 -fno-align-functions -fno-align-loops
0.37s-S -O2
จากนั้นย้ายชุดประกอบด้วยตนเองadd()
หลังจากwork()
0.37 วินาที-O2
0.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
ไม่ใช่เฉพาะที่ระบุไว้ด้านบน แคชคิดถึงมากและมีเสียงดังมากและไม่มีความสัมพันธ์กับเวลาดำเนินการ