Przeglądałem dokumentację i pytania / odpowiedzi i widziałem o tym wspomniane. Przeczytałem krótki opis, stwierdzając, że programista byłby w zasadzie obietnicą, że wskaźnik nie zostanie użyty do wskazania w innym miejscu.
Czy ktoś może zaoferować realistyczne przypadki, w których warto z tego skorzystać?
c
gcc
c99
restrict-qualifier
użytkownik90052
źródło
źródło
memcpy
vsmemmove
jest jednym kanonicznym przykładem.restrict
-kwalifikowanie argumentów w celumemcpy
umożliwienia w zasadzie agresywnej optymalizacji naiwnej implementacji oraz 2) samo wywołaniememcpy
pozwala kompilatorowi założyć, że podane mu argumenty nie są aliasami, co może pozwolić na pewną optymalizację wokółmemcpy
wywołania.memcpy(anything, anything, 0);
za brak operacji i zapewnić, że jeślip
jest wskaźnikiem do co najmniejn
zapisywalnych bajtówmemcpy(p,p,n)
; nie będzie miał żadnych niepożądanych skutków ubocznych. Takie przypadki mogą powstać ...Odpowiedzi:
restrict
mówi, że wskaźnik jest jedyną rzeczą, która uzyskuje dostęp do obiektu leżącego pod spodem. Eliminuje to możliwość aliasingu wskaźnika, umożliwiając lepszą optymalizację przez kompilator.Załóżmy na przykład, że mam maszynę ze specjalistycznymi instrukcjami, która może pomnożyć wektory wektorów liczb w pamięci, i mam następujący kod:
Potrzeby kompilatora prawidłowo obsługiwać razie
dest
,src1
isrc2
nakładania, co oznacza, że musi wykonać jedną mnożenia na raz, od początku do końca. Poprzezrestrict
kompilator jest wolna, aby zoptymalizować ten kod za pomocą instrukcji wektorowych.Wikipedia ma wpis na
restrict
innym przykładzie tutaj .źródło
dest
nakłada się na którykolwiek z wektorów źródłowych. Dlaczego miałby nie być problem, jeślisrc1
isrc2
nakładanie?Przykład Wikipedia jest bardzo pouczające.
Wyraźnie pokazuje, w jaki sposób można zapisać jedną instrukcję montażu .
Bez ograniczeń:
Pseudo-montaż:
Z ograniczeniem:
Pseudo-montaż:
Czy GCC naprawdę to robi?
GCC 4.8 Linux x86-64:
Z
-O0
są takie same.Z
-O3
:Dla niewtajemniczonych konwencja wywoływania to:
rdi
= pierwszy parametrrsi
= drugi parametrrdx
= trzeci parametrWynik GCC był jeszcze wyraźniejszy niż artykuł na wiki: 4 instrukcje vs 3 instrukcje.
Tablice
Do tej pory mamy oszczędności na pojedynczych instrukcjach, ale jeśli wskaźnik reprezentuje tablice, które mają być zapętlone, co jest częstym przypadkiem użycia, wówczas można zapisać kilka instrukcji, jak wspomniano w supercat .
Rozważ na przykład:
Z tego powodu
restrict
inteligentny kompilator (lub człowiek) może zoptymalizować to w celu:który jest potencjalnie znacznie bardziej wydajny, ponieważ może być zoptymalizowany pod kątem montażu w przyzwoitej implementacji libc (jak glibc): czy lepiej jest używać std :: memcpy () lub std :: copy () pod względem wydajności?
Czy GCC naprawdę to robi?
GCC 5.2.1.Linux x86-64 Ubuntu 15.10:
Z
-O0
, oba są takie same.Z
-O3
:z ograniczeniem:
Dwa
memset
połą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.
C99
Spójrzmy na standard dla kompletności.
restrict
mó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ęcej optymalizacji czasu kompilacji.
Jeśli dzwoniący nie przestrzega
restrict
umowy, niezdefiniowane zachowanie.Projekt C99 N1256 6.7.3 / 7 „Kwalifikatory typu” mówi:
oraz 6.7.3.1 „Formalna definicja ograniczenia” podaje krwawe szczegóły.
Ścisła zasada aliasingu
Słowo
restrict
kluczowe wpływa tylko na wskaźniki kompatybilnych typów (np. Dwaint*
), 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?
Zobacz też
restrict
, ale GCC ma__restrict__
jako rozszerzenie: Co oznacza słowo kluczowe ograniczające w C ++?__attribute__((malloc))
, który mówi, że zwracana wartość funkcji nie jest aliasowana do niczego: GCC: __attribute __ ((malloc))źródło
void zap(char *restrict p1, char *restrict p2) { for (int i=0; i<50; i++) { p1[i] = 4; p2[i] = 9; } }
, kwalifikatory ograniczające pozwolą kompilatorowi przepisać kod jako „memset (p1,4,50); memset (p2,9,50);”. Ograniczenie znacznie przewyższa aliasing oparty na typach; Szkoda, że kompilatory koncentrują się bardziej na tym drugim.__restrict
. W przeciwnym razie podwójne podkreślenia mogą zostać źle zinterpretowane jako wskazówka, że krzyczysz.