C99 'restrict' anahtar kelimesinin gerçekçi kullanımı?


183

Bazı belgelere ve sorulara / cevaplara göz atıyordum ve bahsettiğini gördüm. Temel olarak programcıdan işaretçinin başka bir yere işaret etmek için kullanılmayacağına dair bir vaat olacağını belirten kısa bir açıklama okudum.

Herkes bunun gerçekten kullanmaya değer bazı gerçekçi durumlar sunabilir?


4
memcpyvs memmovekanonik bir örnektir.
Alexandre

@AlexandreC .: Bunun özellikle geçerli olduğunu düşünmüyorum, çünkü bir "kısıtla" niteleyicisinin olmaması program mantığının aşırı yük kaynağı ve hedefi ile çalışacağı anlamına gelmez, ne de böyle bir niteleyicinin varlığı çağrılan bir yöntemi önler kaynak ve hedefin örtüşüp örtüşmeyeceğini belirleme ve eğer öyleyse, dest yerine src'den türetildiği için onu takma adına izin verilecek src + (dest-src) ile değiştirme.
supercat

@supercat: Bu yüzden bir yorum olarak koydum. Bununla birlikte, 1) - prensip olarak naif bir uygulamanın agresif bir şekilde optimize restrictedilmesini memcpymümkün kılan argümanların nitelendirilmesi ve 2) sadece çağrı memcpyyapılması, derleyicinin kendisine verilen argümanların memcpyçağrı etrafında bazı optimizasyonlara izin vermediğini varsaymasını sağlar .
Alexandre

@AlexandreC .: Çoğu platformdaki bir derleyicinin, "kısıtlama" ile bile saf bir memcpy'ı hedefe uyarlanmış sürüm kadar verimli bir yere yakınlaştırması çok zor olurdu. Çağrı tarafı optimizasyonları "kısıtla" anahtar kelimesini gerektirmez ve bazı durumlarda bunları kolaylaştırmak için çabalar karşı üretken olabilir. Örneğin, memcpy'nin birçok uygulaması, sıfır ekstra maliyetle, işlem memcpy(anything, anything, 0);yapılmaması olarak değerlendirilebilir ve eğer pen azından nyazılabilir baytlara bir işaretçi ise memcpy(p,p,n); hiçbir olumsuz yan etkisi olmayacaktır. Bu gibi durumlar ortaya çıkabilir ...
supercat

... doğal olarak belirli uygulama kodu türlerinde (örneğin, bir öğeyi kendisiyle değiştiren bir tür rutin) ve olumsuz yan etkileri olmayan uygulamalarda, bu davaların genel durum kodu tarafından ele alınmasına izin vermek, özel durum testleri eklemek için. Ne yazık ki, bazı derleyici yazarları, programcıların derleyicinin optimize edemeyeceği kod eklemesini gerektirmenin daha iyi olduğunu düşünüyor, böylece derleyiciler çok nadiren istismar edecek "optimizasyon fırsatlarını" kolaylaştırmak için.
supercat

Yanıtlar:


182

restrictaltta yatan nesneye erişen tek şey işaretçinin olduğunu söylüyor. Derleyici tarafından daha iyi optimizasyon sağlayarak işaretçi takma potansiyelini ortadan kaldırır.

Örneğin, bellekte sayıların vektörlerini çoğaltabilecek özel talimatlara sahip bir makinem olduğunu ve aşağıdaki kodu aldığımı varsayalım:

void MultiplyArrays(int* dest, int* src1, int* src2, int n)
{
    for(int i = 0; i < n; i++)
    {
        dest[i] = src1[i]*src2[i];
    }
}

Derleyici ihtiyaçlarını doğru olmadığını işlemek için dest, src1ve src2baştan sona, teker teker çarpma yapmanız gerekir anlamına çakışma. Sahip olarak restrict, derleyici vektör talimatları kullanılarak bu kodu optimize etmek için serbesttir.

Wikipedia'nın buradarestrict başka bir örnekle bir girişi var .


3
@Michael - Yanlış bir şey yapmıyorsam, sorun yalnızca destkaynak vektörlerin herhangi biriyle çakıştığında olur. Çakışma src1ve src2çakışma durumunda neden bir sorun olur ?
15:15

1
restrict normalde yalnızca değiştirilmiş bir nesneyi işaret ederken bir etkiye sahiptir, bu durumda gizli yan etkilerin dikkate alınmasını gerektirmediğini varsayar. Çoğu derleyici bunu vektörleştirmeyi kolaylaştırmak için kullanır. Msvc, bu amaçla veri çakışması için çalışma zamanı denetimi kullanır.
tim18

Register döngüsünün for loop değişkenine eklenmesi, restrict eklemenin yanı sıra daha hızlı hale getirir.

2
Aslında, register anahtar sözcüğü yalnızca tavsiye niteliğindedir. Ve 2000 yılından beri derleyicilerde, örnekteki i (ve karşılaştırma için n) register anahtar sözcüğünü kullansanız da kullanmasanız da bir kayıt defterine optimize edilecektir.
Mark Fischler

154

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ü restrictakı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

    memsetBeklendiğ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.

restrictiki 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 restrictsö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ı

restrictAnahtar 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


9
"Kısıtla" niteleyicisi aslında çok daha büyük tasarruflar sağlayabilir. Örneğin void zap(char *restrict p1, char *restrict p2) { for (int i=0; i<50; i++) { p1[i] = 4; p2[i] = 9; } }, kısıtlama niteleyicileri derleyicinin kodu "memset (p1,4,50); memset (p2,9,50);" olarak yeniden yazmasına izin verir. Kısıtlama, tür temelli diğer adlandırmadan çok daha üstündür; bu ikincilere daha çok odaklanan bir utanç.
supercat

@supercat harika bir örnek, cevaplamaya eklendi.
Ciro Santilli 法轮功 冠状 at 六四 事件 法轮功

2
@ tim18: "restrict" anahtar kelimesi, agresif tür tabanlı optimizasyonun bile yapamayacağı birçok optimizasyonu etkinleştirebilir. Ayrıca, dilde "kısıtlamanın" varlığı - agresif tür temelli takma adın aksine - görevlerin yokluğunda mümkün olduğu kadar verimli bir şekilde yerine getirilmesini asla imkansız kılmaz ("kısıtla" ile kırılacak olan kod basitçe kullanmayın, agresif TBAA tarafından kırılan kod genellikle daha az verimli bir şekilde yeniden yazılmalıdır).
supercat

2
@ tim18: Geri çubuklarında çift alt çizgi içeren şeyleri olduğu gibi çevreleyin __restrict. Aksi takdirde, çift alt çizgiler, bağırdığınızın bir göstergesi olarak yanlış yorumlanabilir.
supercat

1
Bağırmamaktan daha önemli olan, alt çizgilerin, yapmaya çalıştığınız nokta ile doğrudan ilgili bir anlamı olması.
remcycles
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.