Что означает ключевое слово restrict в C++?

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

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

7 ответов

Кристер Эриксон в своей статье " Оптимизация памяти" говорит, что пока 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:

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

Edit2

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

Load-hit-store и ключевое слово __restrict

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

Как говорили другие, если для 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__

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

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

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

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

С , compilerможет делать сложные оптимизации, так как программист гарантировал украшенный pointersуказывают на диапазоны данных, которые определенно не будут перекрывать друг друга.

Обычно это так, поэтому для достижения высокой производительности в большинстве случаев можно поставить __restrict__декоратор pointersв вашем коде.

Другие вопросы по тегам