2009-04-22 8 views
135

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

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

+11

'restrict' является c99 ключевое слово. Да, Rpbert S. Barnes, я знаю, что большинство компиляторов поддерживают '__restrict__'. Вы заметите, что что-либо с двойными подчеркиваниями является, по определению, специфичным для реализации и, следовательно, _NOT C++ _, но его конкретной версией для компилятора. – KitsuneYMG

+5

Что? Просто потому, что конкретная реализация не делает ее не C++; C++ допускает конкретные действия для реализации явно, и не запрещает его или не делает его не C++. – Alice

+2

@Alice KitsuneYMG означает, что он не является частью ISO C++ и вместо этого считается расширением C++. Создателям компилятора разрешено создавать и распространять свои собственные расширения, которые сосуществуют с ISO C++ и действуют как часть обычного или непереносимого неофициального дополнения к C++.Примерами могут служить старые Managed C++ от MS и их более поздний C++/CLI. Другими примерами могут быть директивы препроцессора и макросы, поставляемые некоторыми компиляторами, такие как общая директива '# warning' или макросы подписи функций (' __PRETTY_FUNCTION__' на GCC, '__FUNCSIG__' на MSVC и т. Д.). –

ответ

17

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

+6

Это не совсем так. По-видимому, это поддерживается некоторыми компиляторами C++, и некоторые люди настоятельно рекомендуют использовать его, когда они доступны, см. Мой ответ ниже. –

+15

@ Robert S Barnes: стандарт C++ не распознает 'ограничение' как ключевое слово. Поэтому мой ответ верен. То, что вы описываете, - это поведение, специфическое для реализации, и то, на что вы * не должны полагаться. – dirkgently

+21

@ dirkgently: При всем уважении, почему бы и нет? Многие проекты привязаны к конкретным нестандартным языковым расширениям, поддерживаемым только конкретными или очень немногими компиляторами. Ядро Linux и gcc приходит на ум. Это не редкость придерживаться конкретного компилятора или даже конкретной ревизии конкретного компилятора для всего полезного срока службы проекта. Не каждая программа должна строго соответствовать. –

10

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

+5

Многие компиляторы C++ поддерживают ключевое слово '__restrict__', которое, насколько я могу судить, идентично. –

4

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

+3

Я бы предположил, что это либо обрабатывается с помощью декларации 'extern C', либо путем молчания, как в случае с компилятором AIX C/C++, который вместо этого обрабатывает ключевое слово' __rerstrict__'. Это ключевое слово также поддерживается в gcc, поэтому код будет скомпилировать то же самое в g ++. –

119

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

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

! Новый стандарт 1999 ANSI/ISO C

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

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

A ограничивают квалифицированный указатель (или ссылка) ...

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

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

Смотрите эту SO опубликовать подробную информацию: Realistic usage of the C99 ‘restrict’ keyword?

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

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

Я также обнаружил, что компании IBM AIX C/C++ compiler supports the __restrict__ keyword.

г ++ также, кажется, поддерживает это как следующая программа компилируется на г ++:

#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:

Demystifying The Restrict Keyword

Edit2

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

Load-hit-stores and the __restrict keyword

Кроме того, Microsoft Visual C++ also supports the __restrict keyword.

+1

Ссылка на бумагу для оптимизации памяти мертва, вот ссылка на аудио из его презентации GDC. http://www.gdcvault.com/play/1022689/Memory – Grimeh

+1

@EnnMichael: Очевидно, что если вы собираетесь использовать его в переносном проекте на C++, вы должны «#ifndef __GNUC__'' #define __restrict__/* no-op */'или аналогичный. И определите его для '__restrict', если определена' _MSC_VER'. –

5

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

+5

Многие компиляторы C++ поддерживают ключевое слово '__restrict__', которое, насколько я могу судить, идентично. –

+13

@Robert: Но ** в C++ ** такого ключевого слова нет. То, что делают отдельные компиляторы, это их собственный бизнес, но он не является частью языка C++. – jalf

53

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

C99

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

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

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

В C99 N1256 draft 6.7.3/7 «Тип классификаторов» говорит:

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

и 6.7.3.1 «Формальное определение ограничения» дает детали gory.

Возможная оптимизация

Wikipedia exampleявляется очень освещения.

Он ясно показывает, как позволяет сохранить одну инструкцию по сборке.

Без ограничения:

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) 

Для непосвященных calling convention является:

  • rdi = первый параметр
  • rsi = Второй параметр
  • rdx = третий параметр

Выход GCC был еще яснее, чем статья wiki: 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) Is it better to use std::memcpy() or std::copy() in terms to performance?, возможно с SIMD instructions.

Без, ограничивайте, эта оптимизация не может быть выполнена, например.рассмотреть следующие вопросы:

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 итераций широкий loop unrolling, которые я не собираюсь приводить здесь :-)

Я не имел терпения сравнить их, но я не верю что ограниченная версия будет быстрее.

Строгие правила наложения спектров

restrict ключевое слово влияет только указатели совместимых типов (например, два int*), потому что строгие правила наложения спектров говорит, что сглаживание несовместимых типов не определено поведение по умолчанию, и поэтому компиляторы могут предположить, что это делает не происходит и не оптимизируется.

См: What is the strict aliasing rule?

ли работа для ссылок?

Согласно ССАГПЗ документации он делает: https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html с синтаксисом:

int &__restrict__ rref 

Существует даже версия для this функций членов:

void T::fn() __restrict__ 
+0

приятный asnwer. Что делать, если строковое сглаживание отключено '-fno-strict-aliasing', тогда' ограничение' не должно иметь никакого значения между указателями того же типа или разных типов, нет? (я ссылаюсь на «Ключевое слово ограничения влияет только на указатели совместимых типов») – user463035818

+0

@ tobi303 Я не знаю! Дайте мне знать, если вы узнаете наверняка ;-) –

+0

@jww да, это лучший способ его формулировки. Обновлено. –