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

182

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

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

Мат
источник
23
restrictэто ключевое слово c99. Да, Рпберт С. Барнс, я знаю, что большинство компиляторов поддерживают __restrict__. Вы заметите, что все с двойным подчеркиванием, по определению, зависит от конкретной реализации и, следовательно, НЕ от C ++ , а от конкретной версии компилятора.
KitsuneYMG
5
Какой? Только потому, что это зависит от реализации, это не делает его C ++; C ++ явно разрешает специфические для реализации вещи, и не запрещает это или делает это не C ++.
Алиса
4
@Alice KitsuneYMG означает, что он не является частью ISO C ++ и вместо этого считается расширением C ++. Создателям компиляторов разрешено создавать и распространять свои собственные расширения, которые сосуществуют с ISO C ++ и действуют как часть обычно менее переносимого неофициального дополнения к C ++. Примерами могут служить старый Managed C ++ от MS и их более поздний C ++ / CLI. Другими примерами могут быть директивы препроцессора и макросы, предоставляемые некоторыми компиляторами, такие как общая #warningдиректива или макросы сигнатур функций ( __PRETTY_FUNCTION__в GCC, __FUNCSIG__в MSVC и т. Д.).
Джастин Тайм - Восстановить Монику
4
@ Алиса Насколько мне известно, C ++ 11 не требует полной поддержки всего C99, как и C ++ 14 или того, что я знаю о C ++ 17. restrictне считается ключевым словом C ++ (см. en.cppreference.com/w/cpp/keyword ) и фактически является единственным упоминанием restrictв стандарте C ++ 11 (см. open-std.org/jtc1/sc22/wg21 /docs/papers/2012/n3337.pdf , копия FDIS с незначительными редакционными изменениями, §17.2 [library.c], страница PDF 413) гласит, что:
Время Джастина - Восстановить Монику
4
@ Алиса Как так? Я изложил ту часть, которая гласит, что restrictона должна быть исключена из (исключены из, исключены из) сигнатуры и семантики функций стандартной библиотеки C, когда эти функции включены в стандартную библиотеку C ++. Или, другими словами, я констатировал тот факт, что если сигнатура функции стандартной библиотеки restrictC содержится в C, restrictключевое слово должно быть удалено из сигнатуры эквивалента C ++.
Джастин Тайм - Восстановить Монику

Ответы:

143

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

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

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

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

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

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

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

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

См. Этот пост SO для деталей: Реалистичное использование ключевого слова C99 'restrict'?

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

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

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

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ключевое слово .

Роберт С. Барнс
источник
2
Ссылка на документ по оптимизации памяти не работает, вот ссылка на аудио из его презентации GDC. gdcvault.com/play/1022689/Memory
Grimeh
1
@EnnMichael: Очевидно, что если вы собираетесь использовать его в переносимом проекте C ++, вам следует #ifndef __GNUC__ #define __restrict__ /* no-op */или что-то подобное. И определить это, __restrictесли _MSC_VERопределено.
Питер Кордес
96

Как уже говорили другие, если для 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 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__
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник
хороший ответ. Что если строгий псевдоним отключен -fno-strict-aliasing, то не restrictдолжно быть никакой разницы между указателями одного типа или разных типов, нет? (Я ссылаюсь на «Ограничить ключевое слово влияет только на указатели совместимых типов»)
idclev 463035818
@ tobi303 Я не знаю! Дайте мне знать, если узнаете наверняка ;-)
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
@jww да, это лучший способ выразить это. Обновлено.
Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
restrictчто-то значит в C ++. Если вы вызываете функцию библиотеки C с restrictпараметрами из программы на C ++, вы должны подчиниться ее последствиям. По сути, если restrictиспользуется в API библиотеки C, это означает что-то для любого, кто вызывает его с любого языка, включая динамический FFI из Lisp.
Каз
22

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

dirkgently
источник
8
Это не совсем так. Очевидно, он поддерживается некоторыми компиляторами C ++, и некоторые люди настоятельно рекомендуют использовать его, когда он доступен, см. Мой ответ ниже.
Роберт С. Барнс
18
@Robert S Barnes: стандарт C ++ не распознает restrictкак ключевое слово. Следовательно, мой ответ верен. То, что вы описываете, это поведение, специфичное для реализации, и то, на что вы не должны полагаться
Dirkgently
27
@dirkgently: При всем уважении, почему бы и нет? Многие проекты привязаны к конкретным нестандартным языковым расширениям, поддерживаемым только конкретными или очень немногими компиляторами. Ядро Linux и gcc приходят на ум. Весьма обычно придерживаться определенного компилятора или даже конкретной ревизии определенного компилятора в течение всего срока полезного использования проекта. Не каждая программа должна быть строго соответствующей.
Роберт С. Барнс
7
@Rpbert S. Barnes: Я не могу больше подчеркивать, почему вы не должны зависеть от поведения, специфичного для реализации. Что касается Linux и gcc - подумайте, и вы поймете, почему они не являются хорошим примером в вашу защиту. Мне еще предстоит увидеть даже умеренно успешную часть программного обеспечения, работающую на одной версии компилятора в течение всего срока его службы.
Dirkgently
16
@Rpbert S. Barnes: вопрос сказал c ++. Не MSVC, не GCC, не AIX. Если acidzombie24 хочет расширения, специфичные для компилятора, он должен был сказать / пометить так.
KitsuneYMG
12

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

размотать
источник
5
Многие компиляторы C ++ поддерживают __restrict__ключевое слово, которое, насколько я могу судить, идентично.
Роберт С. Барнс
Он имеет все отношение к C ++, потому что программы на C ++ вызывают библиотеки C, а библиотеки C используют restrict. Поведение программы на C ++ становится неопределенным, если оно нарушает ограничения, подразумеваемые restrict.
Каз
@kaz Совершенно неправильно. Это не имеет ничего общего с C ++, потому что это не ключевое слово или особенность C ++, и если вы используете заголовочные файлы C в C ++, вы должны удалить restrictключевое слово. Конечно, если вы передаете псевдонимы-указатели в функцию C, которая объявляет их ограниченными (что вы можете сделать из C ++ или C), тогда они не определены, но это на вас.
Джим Балтер
@JimBalter Я вижу, так что вы говорите, что программы на C ++ вызывают библиотеки C, а библиотеки C используют restrict. Поведение программы на C ++ становится неопределенным, если оно нарушает ограничения, подразумеваемые restrict. Но это на самом деле не имеет ничего общего с C ++, потому что это «на вас».
Каз
5

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

Муравей
источник
5
Многие компиляторы C ++ поддерживают __restrict__ключевое слово, которое, насколько я могу судить, идентично.
Роберт С. Барнс
18
@ Роберт: Но в C ++ такого ключевого слова нет . То, что делают отдельные компиляторы, это их личное дело, но это не является частью языка C ++.
Джалф
4

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

Йохан Буле
источник
3
Я бы предположил, что это либо обрабатывается с помощью extern Cобъявления, либо путем его молча отбрасывания, как в случае с компилятором AIX C / C ++, который вместо этого обрабатывает __rerstrict__ключевое слово. Это ключевое слово также поддерживается в gcc, поэтому код будет компилироваться в g ++.
Роберт С. Барнс