Что означает ключевое слово restrict в C ++? - PullRequest
167 голосов
/ 22 апреля 2009

Я всегда был не уверен, что означает ключевое слово restrict в C ++?

Означает ли это, что два или более указателя, данных функции, не перекрываются? Что еще это значит?

Ответы [ 6 ]

132 голосов
/ 27 декабря 2009

В своей статье Оптимизация памяти Кристер Эриксон говорит, что, хотя restrict еще не является частью стандарта C ++, он поддерживается многими компиляторами, и он рекомендует использовать его при наличии:

ограничить ключевое слово

! Новое в стандарте ANSI / ISO C 1999 года

! Пока не в стандарте C ++, но поддерживается многими компиляторами C ++

! Только подсказка, поэтому может ничего не делать и при этом соответствовать

Ограниченный указатель (или ссылка) ...

! ... это в основном обещаю компилятору, что для область указателя, цель указателя будет только доступ через этот указатель (и указатели скопированы из него).

В компиляторах C ++, которые его поддерживают, он, вероятно, должен вести себя так же, как в C.

Подробнее см. В этом сообщении: Реалистичное использование ключевого слова C99 "restrict"?

Потратьте полчаса на просмотр статьи Эриксона, это интересно и стоит времени.

Редактировать

Я также обнаружил, что компилятор IBM AIX C / C ++ поддерживает ключевое слово __restrict__ .

.

g ++ также, кажется, поддерживает это, поскольку следующая программа аккуратно компилируется на 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;
}

Я также нашел хорошую статью об использовании restrict:

Демистификация ключевого слова Restrict

Edit2

Я наткнулся на статью, в которой конкретно обсуждается использование restrict в программах на C ++:

Хранилища загрузок и ключевое слово __restrict

Кроме того, Microsoft Visual C ++ также поддерживает ключевое слово __restrict .

80 голосов

Как уже говорили другие, если означает ничего, как в C ++ 14 , так что давайте рассмотрим расширение __restrict__ GCC, которое делает то же самое, что и C99 restrict.

C99

restrict говорит, что два указателя не могут указывать на перекрывающиеся области памяти. Чаще всего используются аргументы функций.

Это ограничивает способ вызова функции, но позволяет оптимизировать компиляцию.

Если вызывающая сторона не соблюдает контракт restrict, неопределенное поведение.

C99 N1256, черновик 6.7.3 / 7 "Спецификаторы типов" гласит:

Предполагаемое использование квалификатора restrict (например, класса хранения регистров) состоит в том, чтобы способствовать оптимизации, и удаление всех экземпляров классификатора из всех блоков предварительной обработки перевода, составляющих соответствующую программу, не меняет его значения (т. Е. Наблюдаемое поведение).

и 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 a may be equal to x.
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
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__
17 голосов
/ 22 апреля 2009

Ничего. Он был добавлен в стандарт C99.

10 голосов
/ 22 апреля 2009

Это является исходным предложением добавить это ключевое слово. Как уже отмечалось, это особенность C99 ; это не имеет ничего общего с C ++.

5 голосов
/ 27 декабря 2009

В C ++ такого ключевого слова нет. Список ключевых слов C ++ можно найти в разделе 2.11 / 1 стандарта языка C ++. restrict - это ключевое слово в C99 версии языка C, а не в C ++.

4 голосов
/ 05 декабря 2009

Поскольку в заголовочных файлах некоторых библиотек C используется ключевое слово, язык C ++ должен будет что-то с этим сделать ... как минимум, игнорируя ключевое слово, поэтому нам не нужно # определять ключевое слово для пустого макроса, чтобы подавить ключевое слово.

...