The Wikipedia Contoh adalah sangat mencerahkan.
Itu jelas menunjukkan bagaimana hal itu memungkinkan untuk menyimpan satu instruksi perakitan .
Tanpa batasan:
void f(int *a, int *b, int *x) {
*a += *x;
*b += *x;
}
Perakitan semu:
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
Dengan batasan:
void fr(int *restrict a, int *restrict b, int *restrict x);
Perakitan semu:
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
Apakah GCC benar-benar melakukannya?
GCC 4.8 Linux x86-64:
gcc -g -std=c99 -O0 -c main.c
objdump -S main.o
Dengan -O0
, mereka sama.
Dengan -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)
Untuk yang belum tahu, konvensi pemanggilan adalah:
rdi
= parameter pertama
rsi
= parameter kedua
rdx
= parameter ketiga
Output GCC bahkan lebih jelas daripada artikel wiki: 4 instruksi vs 3 instruksi.
Array
Sejauh ini kami memiliki penghematan instruksi tunggal, tetapi jika pointer mewakili array yang akan dilewati, kasus penggunaan umum, maka banyak instruksi dapat disimpan, seperti yang disebutkan oleh supercat .
Pertimbangkan misalnya:
void f(char *restrict p1, char *restrict p2) {
for (int i = 0; i < 50; i++) {
p1[i] = 4;
p2[i] = 9;
}
}
Karena itu restrict
, kompiler pintar (atau manusia), dapat mengoptimalkannya untuk:
memset(p1, 4, 50);
memset(p2, 9, 50);
yang berpotensi jauh lebih efisien karena perakitan dapat dioptimalkan pada implementasi libc yang layak (seperti glibc): Apakah lebih baik menggunakan std :: memcpy () atau std :: copy () dalam hal kinerja?
Apakah GCC benar-benar melakukannya?
GCC 5.2.1.Linux x86-64 Ubuntu 15.10:
gcc -g -std=c99 -O0 -c main.c
objdump -dr main.o
Dengan -O0
, keduanya sama.
Dengan -O3
:
dengan batasan:
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
Dua memset
panggilan seperti yang diharapkan.
tanpa batasan: tidak ada panggilan stdlib, hanya loop lebar iterasi 16 terbuka yang saya tidak ingin mereproduksi di sini :-)
Saya belum memiliki kesabaran untuk membandingkan mereka, tetapi saya percaya bahwa versi pembatasan akan lebih cepat.
C99
Mari kita lihat standar demi kelengkapan.
restrict
mengatakan bahwa dua petunjuk tidak dapat menunjuk ke wilayah memori yang tumpang tindih. Penggunaan paling umum adalah untuk argumen fungsi.
Ini membatasi bagaimana fungsi dapat dipanggil, tetapi memungkinkan untuk lebih banyak waktu kompilasi optimasi.
Jika penelepon tidak mengikuti restrict
kontrak, perilaku tidak terdefinisi.
The C99 N1256 rancangan 6.7.3 / 7 "Jenis kualifikasi" mengatakan:
Tujuan penggunaan pembatasan kualifikasi (seperti kelas penyimpanan register) adalah untuk mempromosikan optimasi, dan menghapus semua instance kualifikasi dari semua unit terjemahan preprocessing yang menyusun program yang sesuai tidak mengubah artinya (yaitu, perilaku yang dapat diamati).
dan 6.7.3.1 "Definisi formal pembatasan" memberikan detail yang mengerikan.
Aturan aliasing yang ketat
Kata restrict
kunci hanya memengaruhi pointer dari tipe yang kompatibel (misalnya dua int*
) karena aturan aliasing yang ketat mengatakan bahwa aliasing tipe yang tidak kompatibel adalah perilaku yang tidak terdefinisi secara default, sehingga kompiler dapat menganggap itu tidak terjadi dan mengoptimalkannya.
Lihat: Apa aturan aliasing yang ketat?
Lihat juga
memcpy
vsmemmove
adalah salah satu contoh kanonik.