Diğerlerinin söylediği gibi, C ++ 14'ten başka bir şey ifade __restrict__
etmiyorsa, C99 ile aynı şeyi yapan GCC uzantısını düşünelim restrict
.
C99
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 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.
Olası bir optimizasyon
Vikipedi örnek olduğunu çok aydınlatıcı.
Bir montaj talimatını nasıl kaydedebileceğini açıkça göstermektedir .
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?
g++
4.8 Linux x86-64:
g++ -g -std=gnu++98 -O0 -c main.cpp
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 talimat vs 3 talimat.
Diziler
Şimdiye kadar tek bir talimat tasarrufumuz var, ancak eğer işaretçi döngü yapılacak dizileri temsil ediyorsa, ortak bir kullanım durumu, o zaman supercat ve michael tarafından belirtildiği gibi bir grup talimat kaydedilebilir .
Örneğin şunu düşünün:
void f(char *restrict p1, char *restrict p2, size_t size) {
for (size_t i = 0; i < size; i++) {
p1[i] = 4;
p2[i] = 9;
}
}
Çünkü restrict
akıllı bir derleyici (veya insan) bunu optimize edebilir:
memset(p1, 4, size);
memset(p2, 9, size);
İyi bir libc uygulamasında (glibc gibi) montaj optimize edilmiş olabileceğinden potansiyel olarak çok daha etkilidir Performans açısından std :: memcpy () veya std :: copy () kullanmak daha mı iyi? , muhtemelen SIMD talimatları ile .
Kısıtlama olmadan, bu optimizasyon yapılamadı, örneğin:
char p1[4];
char *p2 = &p1[1];
f(p1, p2, 3);
Sonra for
sürüm yapar:
p1 == {4, 4, 4, 9}
ederken memset
versiyonu yapar:
p1 == {4, 9, 9, 9}
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.
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?
Referanslar için çalışıyor mu?
GCC belgelerine göre şunu yapar: https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html sözdizimi ile:
int &__restrict__ rref
this
Üye işlevlerinin bir sürümü bile vardır :
void T::fn () __restrict__
restrict
bir c99 anahtar kelimesidir. Evet, Rpbert S. Barnes, çoğu derleyicinin desteklediğini biliyorum__restrict__
. Çift alt çizgisi olan herhangi bir şeyin, tanım gereği, uygulamaya özel ve dolayısıyla C ++ DEĞİL olduğunu , ancak bunun derleyiciye özgü bir sürümü olduğunu göreceksiniz .