Vikipedi örnek olduğunu çok aydınlatıcı.
Bir montaj talimatının nasıl kaydedileceğini açıkça gösterir .
Kısıtlama olmadan:
void f(int *a, int *b, int *x) {
*a += *x;
*b += *x;
}
Sözde montaj:
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
Kısıtlama ile:
void fr(int *restrict a, int *restrict b, int *restrict x);
Sözde montaj:
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 gerçekten yapıyor mu?
GCC 4.8 Linux x86-64:
gcc -g -std=c99 -O0 -c main.c
objdump -S main.o
İle -O0
, onlar aynı.
İle -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)
Deneyimsiz olanlar için çağıran sözleşme şöyledir:
rdi
= ilk parametre
rsi
= ikinci parametre
rdx
= üçüncü parametre
GCC çıktısı wiki makalesinden bile daha netti: 4 talimatlar vs 3 talimatlar.
Diziler
Şimdiye kadar tek talimat tasarrufumuz var, ancak eğer işaretçi döngü yapılacak dizileri temsil ediyorsa, ortak bir kullanım durumu, o zaman supercat tarafından belirtildiği gibi bir grup talimat kaydedilebilir .
Örneğin şunu düşünün:
void f(char *restrict p1, char *restrict p2) {
for (int i = 0; i < 50; i++) {
p1[i] = 4;
p2[i] = 9;
}
}
Çünkü restrict
akıllı bir derleyici (veya insan) bunu aşağıdakileri optimize edebilir:
memset(p1, 4, 50);
memset(p2, 9, 50);
iyi bir libc uygulamasında (glibc gibi) derleme için optimize edilebileceğinden potansiyel olarak çok daha verimlidir: Performans açısından std :: memcpy () veya std :: copy () kullanmak daha mı iyidir?
GCC gerçekten yapıyor mu?
GCC 5.2.1. Linux x86-64 Ubuntu 15.10:
gcc -g -std=c99 -O0 -c main.c
objdump -dr main.o
İle -O0
, ikisi de aynı.
İle -O3
:
kısıtlama ile:
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
Beklendiği gibi iki çağrı.
kısıtlama olmadan: hiçbir stdlib çağrıları, sadece burada çoğaltmak niyetinde değil 16 yineleme geniş bir döngü unrolling :-)
Ben onları karşılaştırmak için sabır yoktu, ama kısıtlama sürümü daha hızlı olacağına inanıyorum.
C99
Tamlık uğruna standarda bakalım.
restrict
iki göstergenin çakışan bellek bölgelerine işaret edemediğini söylüyor. En yaygın kullanım işlevi işlev bağımsız değişkenleridir.
Bu, işlevin nasıl çağrılacağını kısıtlar, ancak daha fazla derleme zamanı optimizasyonu sağlar.
Arayan restrict
sözleşmeyi takip etmezse , tanımsız davranış.
C99 N1256 taslak 6.7.3 / 7 "Tip eleme" diyor:
Kısıtlayıcı niteleyicinin amaçlanan kullanımı (yazmaç saklama sınıfı gibi) optimizasyonu teşvik etmek ve uygun bir program oluşturan tüm önişleme çeviri birimlerinden niteleyicinin tüm örneklerinin silinmesi anlamını değiştirmez (yani gözlemlenebilir davranış).
ve 6.7.3.1 "Kısıtlamanın resmi tanımı" kanlı detayları verir.
Katı takma adlandırma kuralı
restrict
Anahtar kelime yalnızca uyumlu türleri işaretçileri (örneğin iki etkileyen int*
sıkı örtüşme kuralları uyumsuz türleri aliasing varsayılan olarak tanımlanmamış davranış olduğu belirtildiği için) ve derleyiciler varsayabiliriz yüzden uzakta gerçekleşmesi ve optimize etmez.
Bkz . Katı takma adlandırma kuralı nedir?
Ayrıca bakınız
memcpy
vsmemmove
kanonik bir örnektir.