Co oznacza słowo kluczowe ograniczające w C ++?

182

Zawsze nie byłem pewien, co oznacza słowo kluczowe ograniczające w C ++?

Czy to oznacza, że ​​dwa lub więcej wskaźników nadanych funkcji nie nakłada się? Co to jeszcze znaczy?

Mata
źródło
23
restrictto słowo kluczowe c99. Tak, Rpbert S. Barnes, wiem, że większość kompilatorów obsługuje __restrict__. Zauważysz, że wszystko z podwójnymi podkreśleniami jest z definicji specyficzne dla implementacji, a więc NIE C ++ , ale jest wersją specyficzną dla kompilatora.
KitsuneYMG
5
Co? Tylko dlatego, że jest specyficzny dla implementacji, nie czyni go C ++; C ++ pozwala na jawnie implementację specyficznych dla implementacji rzeczy i nie zabrania tego ani nie renderuje w C ++.
Alice
4
@Alice KitsuneYMG oznacza, że ​​nie jest częścią ISO C ++, a zamiast tego jest uważana za rozszerzenie C ++. Twórcy kompilatorów mogą tworzyć i dystrybuować własne rozszerzenia, które współistnieją z ISO C ++ i działają jako część zwykle mniej lub nieprzenośnego nieoficjalnego dodatku do C ++. Przykładami mogą być stare Managed C ++ MS i ich nowsze C ++ / CLI. Innymi przykładami byłyby dyrektywy i makra preprocesora dostarczane przez niektóre kompilatory, takie jak wspólna #warningdyrektywa, lub makra sygnatur funkcji ( __PRETTY_FUNCTION__w GCC, __FUNCSIG__w MSVC itp.).
Justin Time - Przywróć Monikę
4
@Alice O ile mi wiadomo, C ++ 11 nie wymaga pełnego wsparcia dla całego C99, podobnie jak C ++ 14 lub to, co wiem o C ++ 17. restrictnie jest uważane za słowo kluczowe C ++ (patrz en.cppreference.com/w/cpp/keyword ), a tak naprawdę jedyna wzmianka o restrictstandardzie C ++ 11 (patrz open-std.org/jtc1/sc22/wg21 /docs/papers/2012/n3337.pdf , kopia FDIS z drobnymi zmianami redakcyjnymi, § 17.2 [biblioteka.c], strona PDF 413) stwierdza, że:
Justin Time - przywróć Monikę
4
@Alice Jak to zrobić? Podałem część, która mówi, że restrictnależy pominąć (wyłączyć, pominąć ) podpisy i semantykę funkcji standardowej biblioteki C, gdy funkcje te są zawarte w standardowej bibliotece C ++. Innymi słowy, podałem fakt, że jeśli podpis standardowej funkcji biblioteki restrictC zawiera C, restrictsłowo kluczowe należy usunąć z podpisu odpowiednika C ++.
Justin Time - Przywróć Monikę

Odpowiedzi:

143

W swoim artykule „ Optymalizacja pamięci” Christer Ericson mówi, że chociaż restrictnie jest jeszcze częścią standardu C ++, jest obsługiwany przez wiele kompilatorów i zaleca jego użycie, gdy jest dostępne:

ogranicz słowo kluczowe

! Nowy standard 1999 ANSI / ISO C.

! Jeszcze nie w standardzie C ++, ale obsługiwany przez wiele kompilatorów C ++

! Tylko wskazówka, więc może nic nie robić i nadal być zgodna

Wskaźnik kwalifikowany do ograniczenia (lub odniesienie) ...

! ... jest w zasadzie obietnicą dla kompilatora, że ​​dla zakresu wskaźnika dostęp do celu wskaźnika będzie możliwy tylko przez ten wskaźnik (i wskaźniki skopiowane z niego).

W kompilatorach C ++, które go obsługują, prawdopodobnie powinien on zachowywać się tak samo jak w C.

Zobacz ten post SO, aby uzyskać szczegółowe informacje: Czy realistyczne jest użycie słowa kluczowego „99 ”C99?

Poświęć pół godziny na przejrzenie gazety Ericsona, jest to interesujące i warte czasu.

Edytować

Odkryłem również, że kompilator AIX C / C ++__restrict__ IBM obsługuje słowo kluczowe .

Wydaje się, że g ++ również to obsługuje, ponieważ następujący program kompiluje się czysto na g ++:

#include <stdio.h>

int foo(int * __restrict__ a, int * __restrict__ b) {
    return *a + *b;
}

int main(void) {
    int a = 1, b = 1, c;

    c = foo(&a, &b);

    printf("c == %d\n", c);

    return 0;
}

Znalazłem też fajny artykuł na temat korzystania z restrict:

Odsłanianie słowa kluczowego Ogranicz

Edytuj2

Natknąłem się na artykuł, który konkretnie omawia użycie ograniczeń w programach C ++:

Load-hit-stores i słowo kluczowe __restrict

Microsoft Visual C ++ obsługuje również __restrictsłowo kluczowe .

Robert S. Barnes
źródło
2
Papierowy link do optymalizacji pamięci nie działa, oto link do dźwięku z jego prezentacji GDC. gdcvault.com/play/1022689/Memory
Grimeh
1
@EnnMichael: Oczywiście, jeśli zamierzasz użyć go w przenośnym projekcie C ++, powinieneś #ifndef __GNUC__ #define __restrict__ /* no-op */lub podobnie. I zdefiniuj to, __restrictjeśli _MSC_VERjest zdefiniowane.
Peter Cordes
96

Jak powiedzieli inni, jeśli nic nie znaczy od C ++ 14 , rozważmy __restrict__rozszerzenie GCC, które robi to samo co C99 restrict.

C99

restrictmówi, że dwa wskaźniki nie mogą wskazywać na nakładające się obszary pamięci. Najczęstszym zastosowaniem są argumenty funkcji.

Ogranicza to sposób wywoływania funkcji, ale pozwala na większą optymalizację kompilacji.

Jeśli dzwoniący nie przestrzega restrictumowy, niezdefiniowane zachowanie.

Projekt C99 N1256 6.7.3 / 7 „Kwalifikatory typu” mówi:

Zamierzonym zastosowaniem kwalifikatora ograniczającego (takiego jak klasa pamięci rejestru) jest promowanie optymalizacji, a usunięcie wszystkich instancji kwalifikatora ze wszystkich jednostek tłumaczenia wstępnego tworzących zgodny program nie zmienia jego znaczenia (tj. Obserwowalnego zachowania).

oraz 6.7.3.1 „Formalna definicja ograniczenia” podaje krwawe szczegóły.

Możliwa optymalizacja

Przykład Wikipedia jest bardzo pouczające.

Wyraźnie pokazuje, jak pozwala zapisać jedną instrukcję montażu .

Bez ograniczeń:

void f(int *a, int *b, int *x) {
  *a += *x;
  *b += *x;
}

Pseudo-montaż:

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

Z ograniczeniem:

void fr(int *restrict a, int *restrict b, int *restrict x);

Pseudo-montaż:

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

Czy GCC naprawdę to robi?

g++ 4.8 Linux x86-64:

g++ -g -std=gnu++98 -O0 -c main.cpp
objdump -S main.o

Z -O0są takie same.

Z -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) 

Dla niewtajemniczonych konwencja wywoływania to:

  • rdi = pierwszy parametr
  • rsi = drugi parametr
  • rdx = trzeci parametr

Wynik GCC był jeszcze wyraźniejszy niż artykuł na wiki: 4 instrukcje vs 3 instrukcje.

Tablice

Do tej pory mamy oszczędności pojedynczych instrukcji, ale jeśli wskaźnik reprezentuje tablice, które mają być zapętlone, co jest powszechnym przypadkiem użycia, wówczas można zapisać kilka instrukcji, o czym wspominają supercat i michael .

Rozważ na przykład:

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;
     }
 }

Z tego powodu restrictinteligentny kompilator (lub człowiek) może zoptymalizować to w celu:

memset(p1, 4, size);
memset(p2, 9, size);

Który jest potencjalnie znacznie bardziej wydajny, ponieważ może być zoptymalizowany pod kątem montażu w porządnej implementacji libc (takiej jak glibc) Czy lepiej jest używać std :: memcpy () lub std :: copy () pod względem wydajności? , ewentualnie z instrukcjami SIMD .

Bez ograniczenia ta optymalizacja nie byłaby możliwa, np. Rozważ:

char p1[4];
char *p2 = &p1[1];
f(p1, p2, 3);

Następnie forwersja tworzy:

p1 == {4, 4, 4, 9}

podczas gdy memsetwersja zapewnia:

p1 == {4, 9, 9, 9}

Czy GCC naprawdę to robi?

GCC 5.2.1.Linux x86-64 Ubuntu 15.10:

gcc -g -std=c99 -O0 -c main.c
objdump -dr main.o

Z -O0, oba są takie same.

Z -O3:

  • z ograniczeniem:

    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
    

    Dwa memsetpołączenia zgodnie z oczekiwaniami.

  • bez ograniczeń: brak wywołań stdlib, tylko rozwijanie pętli o szerokości 16 iteracji, których nie zamierzam tu odtwarzać :-)

Nie miałem cierpliwości, by je testować, ale wierzę, że wersja z ograniczeniami będzie szybsza.

Ścisła zasada aliasingu

Słowo restrictkluczowe wpływa tylko na wskaźniki kompatybilnych typów (np. Dwa int*), ponieważ surowe reguły aliasingu mówią, że aliasing niezgodnych typów jest domyślnie niezdefiniowanym zachowaniem, więc kompilatory mogą założyć, że tak się nie dzieje i zoptymalizować.

Zobacz: jaka jest ścisła zasada aliasingu?

Czy to działa w odniesieniu do referencji?

Zgodnie z dokumentacją GCC robi to: https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html ze składnią:

int &__restrict__ rref

Istnieje nawet wersja thisfunkcji członkowskich:

void T::fn () __restrict__
Ciro Santilli
źródło
niezły asnwer. Co się stanie, jeśli ścisłe aliasing jest wyłączone -fno-strict-aliasing, to nie restrictpowinno robić różnicy między wskaźnikami tego samego typu lub różnych typów, nie? (Mam na myśli „
Słowo
@ tobi303 Nie wiem! Daj mi znać, jeśli się dowiesz ;-)
Ciro Santilli 9 冠状 病 六四 事件 法轮功
@ jww tak, to lepszy sposób na sformułowanie tego. Zaktualizowano
Ciro Santilli 25 冠状 病 六四 事件 法轮功
restrictznaczy coś w C ++. Jeśli wywołasz funkcję biblioteki C z restrictparametrami z programu C ++, musisz przestrzegać jej konsekwencji. Zasadniczo, jeśli restrictjest używany w interfejsie API biblioteki C, oznacza to coś dla każdego dzwoniącego z dowolnego języka, w tym dynamicznego FFI z Lisp.
Kaz
22

Nic. Został dodany do standardu C99.

bezpośrednio
źródło
8
To nie do końca prawda. Najwyraźniej jest obsługiwany przez niektóre kompilatory C ++, a niektórzy zdecydowanie zalecają jego użycie, gdy jest dostępne, zobacz moją odpowiedź poniżej.
Robert S. Barnes,
18
@Robert S Barnes: Standard C ++ nie rozpoznaje restrictsłowa kluczowego. Dlatego moja odpowiedź jest prawidłowa. Opisujesz zachowanie specyficzne dla implementacji i coś, na czym tak naprawdę nie powinieneś polegać.
bezpośrednio
27
@dirkgently: Z całym szacunkiem, dlaczego nie? Wiele projektów jest powiązanych z określonymi niestandardowymi rozszerzeniami języka obsługiwanymi tylko przez określone lub bardzo nieliczne kompilatory. Kernel Linux i gcc przychodzą mi na myśl. Często zdarza się trzymać określonego kompilatora, a nawet konkretnej wersji konkretnego kompilatora przez cały okres użytkowania projektu. Nie każdy program musi być ściśle zgodny.
Robert S. Barnes,
7
@Rbert S. Barnes: Nie mogę już dłużej podkreślać, dlaczego nie należy polegać na specyficznych zachowaniach związanych z implementacją. Co do Linuksa i gcc - zastanów się, a zobaczysz, dlaczego nie są dobrym przykładem w twojej obronie. Do tej pory widzę, że nawet umiarkowanie udane oprogramowanie działało na jednej wersji kompilatora przez cały okres jego użytkowania.
bezpośrednio
16
@Rbert S. Barnes: Pytanie brzmiało c ++. Nie MSVC, nie gcc, nie AIX. Jeśli acidzombie24 chciał rozszerzeń specyficznych dla kompilatora, to powinien był to powiedzieć / oznaczyć.
KitsuneYMG
12

To jest oryginalna propozycja dodania tego słowa kluczowego. Jak jednak wyraźnie zaznaczono, jest to funkcja C99 ; nie ma to nic wspólnego z C ++.

rozwijać
źródło
5
Wiele kompilatorów C ++ obsługuje __restrict__słowo kluczowe, które jest identyczne, o ile wiem.
Robert S. Barnes,
Ma wszystko wspólnego z C ++, ponieważ programy C ++ wywołują biblioteki C, a biblioteki C używają restrict. Zachowanie programu C ++ staje się niezdefiniowane, jeśli narusza ograniczenia implikowane przez restrict.
Kaz
@kaz Zupełnie źle. Nie ma to nic wspólnego z C ++, ponieważ nie jest słowem kluczowym ani funkcją C ++, a jeśli używasz plików nagłówkowych C w C ++, musisz usunąć restrictsłowo kluczowe. Oczywiście, jeśli przekażesz aliasy wskaźników do funkcji C, która deklaruje, że są ograniczone (co możesz zrobić zarówno z C ++, jak i C), to jest niezdefiniowana, ale to zależy od ciebie.
Jim Balter
@JimBalter Rozumiem, więc mówisz, że programy C ++ wywołują biblioteki C, a biblioteki C używają restrict. Zachowanie programu C ++ staje się niezdefiniowane, jeśli narusza ograniczenia wynikające z ograniczenia. Ale tak naprawdę nie ma to nic wspólnego z C ++, ponieważ jest „na ciebie”.
Kaz
5

W C ++ nie ma takiego słowa kluczowego. Lista słów kluczowych C ++ znajduje się w sekcji 2.11 / 1 standardu języka C ++. restrictjest słowem kluczowym w wersji C99 języka C, a nie w C ++.

Mrówka
źródło
5
Wiele kompilatorów C ++ obsługuje __restrict__słowo kluczowe, które jest identyczne, o ile wiem.
Robert S. Barnes,
18
@Robert: Ale w C ++ nie ma takiego słowa kluczowego . Poszczególne kompilatory zajmują się własną działalnością, ale nie są częścią języka C ++.
lipiec
4

Ponieważ pliki nagłówkowe z niektórych bibliotek C używają słowa kluczowego, język C ++ będzie musiał coś z tym zrobić .. przynajmniej, ignorując słowo kluczowe, więc nie musimy # definiować słowa kluczowego do pustego makra, aby stłumić słowo kluczowe .

Johan Boulé
źródło
3
Sądzę, że jest to obsługiwane przy użyciu extern Cdeklaracji lub po cichu upuszczane, jak ma to miejsce w przypadku kompilatora AIX C / C ++, który zamiast tego obsługuje __rerstrict__słowo kluczowe. To słowo kluczowe jest również obsługiwane w gcc, dzięki czemu kod będzie kompilować to samo w g ++.
Robert S. Barnes