วิกิพีเดียตัวอย่างเป็นมากส่องสว่าง
มันแสดงให้เห็นอย่างชัดเจนว่าจะช่วยให้การบันทึกการเรียนการสอนการประกอบหนึ่ง
โดยไม่ จำกัด :
void f(int *a, int *b, int *x) {
*a += *x;
*b += *x;
}
ประกอบหลอก:
load R1 ← *x ; Load the value of x pointer
load R2 ← *a ; Load the value of a pointer
add R2 += R1 ; Perform Addition
set R2 → *a ; Update the value of a pointer
; Similarly for b, note that x is loaded twice,
; because x may point to a (a aliased by x) thus
; the value of x will change when the value of a
; changes.
load R1 ← *x
load R2 ← *b
add R2 += R1
set R2 → *b
ด้วยข้อ จำกัด :
void fr(int *restrict a, int *restrict b, int *restrict x);
ประกอบหลอก:
load R1 ← *x
load R2 ← *a
add R2 += R1
set R2 → *a
; Note that x is not reloaded,
; because the compiler knows it is unchanged
; "load R1 ← *x" is no longer needed.
load R2 ← *b
add R2 += R1
set R2 → *b
GCC ทำจริงหรือ
GCC 4.8 Linux x86-64:
gcc -g -std=c99 -O0 -c main.c
objdump -S main.o
ด้วย-O0
พวกเขาเหมือนกัน
ด้วย-O3
:
void f(int *a, int *b, int *x) {
*a += *x;
0: 8b 02 mov (%rdx),%eax
2: 01 07 add %eax,(%rdi)
*b += *x;
4: 8b 02 mov (%rdx),%eax
6: 01 06 add %eax,(%rsi)
void fr(int *restrict a, int *restrict b, int *restrict x) {
*a += *x;
10: 8b 02 mov (%rdx),%eax
12: 01 07 add %eax,(%rdi)
*b += *x;
14: 01 06 add %eax,(%rsi)
สำหรับการฝึกหัดการเรียกคือ:
rdi
= พารามิเตอร์แรก
rsi
= พารามิเตอร์ที่สอง
rdx
= พารามิเตอร์ที่สาม
ผลลัพธ์ของ GCC นั้นชัดเจนยิ่งกว่าบทความ wiki: 4 คำแนะนำกับ 3 คำแนะนำ
อาร์เรย์
จนถึงขณะนี้เรามีเงินออมคำสั่งเดียว แต่ถ้าชี้แทนอาร์เรย์ที่จะคล้องมากกว่ากรณีการใช้งานทั่วไปแล้วพวงของคำสั่งจะถูกบันทึกไว้เป็นที่กล่าวถึงโดยSuperCat
ลองพิจารณาตัวอย่าง:
void f(char *restrict p1, char *restrict p2) {
for (int i = 0; i < 50; i++) {
p1[i] = 4;
p2[i] = 9;
}
}
เนื่องจากrestrict
คอมไพเลอร์สมาร์ท (หรือมนุษย์) สามารถเพิ่มประสิทธิภาพให้กับ:
memset(p1, 4, 50);
memset(p2, 9, 50);
ซึ่งอาจมีประสิทธิภาพมากขึ้นเนื่องจากอาจมีการเพิ่มประสิทธิภาพการประกอบในการใช้งาน libc ที่เหมาะสม (เช่น glibc): มันจะดีกว่าถ้าใช้ std :: memcpy () หรือ std :: copy () ในแง่ของประสิทธิภาพ?
GCC ทำจริงหรือ
GCC 5.2.1.Linux x86-64 Ubuntu 15.10:
gcc -g -std=c99 -O0 -c main.c
objdump -dr main.o
ด้วย-O0
ทั้งคู่เหมือนกัน
ด้วย-O3
:
ด้วยข้อ จำกัด :
3f0: 48 85 d2 test %rdx,%rdx
3f3: 74 33 je 428 <fr+0x38>
3f5: 55 push %rbp
3f6: 53 push %rbx
3f7: 48 89 f5 mov %rsi,%rbp
3fa: be 04 00 00 00 mov $0x4,%esi
3ff: 48 89 d3 mov %rdx,%rbx
402: 48 83 ec 08 sub $0x8,%rsp
406: e8 00 00 00 00 callq 40b <fr+0x1b>
407: R_X86_64_PC32 memset-0x4
40b: 48 83 c4 08 add $0x8,%rsp
40f: 48 89 da mov %rbx,%rdx
412: 48 89 ef mov %rbp,%rdi
415: 5b pop %rbx
416: 5d pop %rbp
417: be 09 00 00 00 mov $0x9,%esi
41c: e9 00 00 00 00 jmpq 421 <fr+0x31>
41d: R_X86_64_PC32 memset-0x4
421: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
428: f3 c3 repz retq
สองmemset
สายตามที่คาดไว้
โดยไม่ จำกัด : ไม่มี STDLIB สายเพียง 16 ย้ำกว้างห่วงคลี่ซึ่งฉันไม่ได้ตั้งใจที่จะทำซ้ำที่นี่ :-)
ฉันไม่ได้มีความอดทนในการเปรียบเทียบพวกเขา แต่ฉันเชื่อว่าการ จำกัด เวอร์ชันจะเร็วขึ้น
C99
ลองดูที่มาตรฐานเพื่อความสมบูรณ์
restrict
บอกว่าตัวชี้สองตัวไม่สามารถชี้ไปที่ส่วนของหน่วยความจำที่ทับซ้อนกันได้ การใช้งานทั่วไปส่วนใหญ่มีไว้สำหรับอาร์กิวเมนต์ของฟังก์ชัน
สิ่งนี้ จำกัด วิธีการเรียกใช้ฟังก์ชัน แต่อนุญาตให้มีการปรับเวลาแบบคอมไพล์ให้ดีขึ้น
หากผู้โทรไม่ปฏิบัติตามrestrict
สัญญาให้ระบุพฤติกรรมที่ไม่ได้กำหนดไว้
C99 N1256 ร่าง 6.7.3 / 7 "ชนิดบ่น" พูดว่า:
การใช้งานที่ จำกัด ของ qualifier ที่ จำกัด (เช่นคลาสหน่วยเก็บข้อมูลรีจิสเตอร์) คือการโปรโมตการออปติไมซ์และการลบอินสแตนซ์ทั้งหมดของ qualifier จากหน่วยการแปลก่อนการประมวลผลทั้งหมดที่ประกอบด้วยโปรแกรมที่สอดคล้องกัน
6.7.3.1 "การจำกัดความหมายอย่างเป็นทางการ" ให้รายละเอียดเต็มไปด้วยเลือด
กฎนามแฝงที่เข้มงวด
restrict
คำหลักที่จะมีผลต่อตัวชี้ประเภทที่เข้ากันได้ (เช่นสองint*
) เพราะกฎระเบียบที่เข้มงวด aliasing บอกว่า aliasing ชนิดเข้ากันไม่ได้เป็นพฤติกรรมที่ไม่ได้กำหนดโดยค่าเริ่มต้นและเพื่อให้คอมไพเลอร์สามารถสันนิษฐานได้ว่ามันไม่ได้เกิดขึ้นและเพิ่มประสิทธิภาพออกไป
ดู: กฎนามแฝงที่เข้มงวดคืออะไร
ดูสิ่งนี้ด้วย
memcpy
vsmemmove
เป็นตัวอย่างที่ยอมรับได้หนึ่งตัวอย่าง