Як говорили інші, якщо C ++ 14 нічого не означає , давайте розглянемо __restrict__
розширення GCC, яке робить те саме, що і C99 restrict
.
C99
restrict
говорить, що два покажчики не можуть вказувати на перекриття областей пам'яті. Найбільш поширене використання для аргументів функції.
Це обмежує те, як можна викликати функцію, але дозволяє отримати більше оптимізацій компіляції.
Якщо абонент не дотримується restrict
договору, не визначена поведінка.
У проекті 6.7.3 / 7 "Кваліфікатори типу" C99 N1256 написано:
Передбачуване використання обмежувального класифікатора (як клас зберігання реєстру) полягає у сприянні оптимізації, а видалення всіх екземплярів класифікатора з усіх попередньо оброблених перекладацьких одиниць, що складають відповідну програму, не змінює її значення (тобто, поведінка, що спостерігається).
та 6.7.3.1 "Формальне визначення обмеження" містить детальні відомості.
Можлива оптимізація
Приклад Вікіпедії є дуже освітлювальним.
Це чітко показує, як це дозволяє зберегти одну інструкцію зі зборки .
Без обмежень:
void f(int *a, int *b, int *x) {
*a += *x;
*b += *x;
}
Псевдозбірка:
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
З обмеженням:
void fr(int *restrict a, int *restrict b, int *restrict x);
Псевдозбірка:
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?
g++
4.8 Linux x86-64:
g++ -g -std=gnu++98 -O0 -c main.cpp
objdump -S main.o
З -O0
, вони однакові.
З -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)
Для неусвідомлених закликає конвенція :
rdi
= перший параметр
rsi
= другий параметр
rdx
= третій параметр
Вихід з GCC був ще чіткішим, ніж стаття Вікі: 4 інструкції проти 3 інструкцій.
Масиви
Поки ми маємо єдину економію інструкцій, але якщо вказівник представляє масиви, які слід перекинути, загальний випадок використання, то купу інструкцій можна зберегти, як згадуються supercat та michael .
Розглянемо для прикладу:
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;
}
}
Через те restrict
, що смарт-компілятор (або людина) може оптимізувати це для:
memset(p1, 4, size);
memset(p2, 9, size);
Що потенційно набагато ефективніше, оскільки це може бути оптимізовано збірка на гідній реалізації libc (наприклад, glibc) Чи краще використовувати std :: memcpy () або std :: copy () з точки зору продуктивності? , можливо, із інструкціями SIMD .
Без обмеження цю оптимізацію неможливо здійснити, наприклад, врахуйте:
char p1[4];
char *p2 = &p1[1];
f(p1, p2, 3);
Тоді for
версія робить:
p1 == {4, 4, 4, 9}
а memset
версія робить:
p1 == {4, 9, 9, 9}
Чи справді це робить GCC?
GCC 5.2.1.Linux x86-64 Ubuntu 15.10:
gcc -g -std=c99 -O0 -c main.c
objdump -dr main.o
З -O0
, обидва однакові.
З -O3
:
з обмеженням:
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
дзвінки, як очікувалося.
без обмежень: жодних дзвінків stdlib, просто розгортання циклу з 16 ітерацій, який я не збираюся відтворювати тут :-)
У мене не було терпіння їх орієнтувати, але я вірю, що обмежена версія буде швидшою.
Суворе правило дозволу
restrict
Ключове слово впливає тільки покажчики сумісних типів (наприклад , два int*
) , оскільки суворі правила накладення спектрів кажуть , що згладжування несумісних типів не визначено поведінку за замовчуванням, і тому компілятори можуть припустити , що це не відбудеться і оптимізує геть.
Дивіться: Що таке суворе правило псевдоніму?
Це працює для довідок?
Згідно з документами GCC це робить: https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html із синтаксисом:
int &__restrict__ rref
Існує навіть версія для this
функцій членів:
void T::fn () __restrict__
restrict
- це ключове слово c99. Так, Рпберт С. Барнс, я знаю, що більшість компіляторів підтримують__restrict__
. Ви зауважите, що все, що має подвійні підкреслення, за визначенням є специфічним для реалізації, і, таким чином, НЕ C ++ , а конкретна версія для компілятора.