Что могут люди сделать из ограничительного квалификатора? - PullRequest
16 голосов
/ 02 октября 2009

Если я правильно понял ключевое слово C99 restrict, то указание ему указателя будет означать, что данные, на которые он ссылается, не будут изменены за спиной компилятора через псевдонимы.

В отличие от этого, я понимаю квалификатор const как документацию, поддерживаемую компилятором, что данный объект не будет изменен за спиной человека, пишущего код. Компилятор может получить подсказку как побочный эффект, но мне как программисту все равно.

Аналогичным образом, было бы уместно рассматривать квалификатор restrict в прототипе функции как требование, чтобы пользователь гарантировал исключительный доступ («избегайте наложения псевдонимов», или, возможно, что-то более сильное) на время вызова? Должно ли оно использоваться как «документация»?

Кроме того, нужно ли что-то понимать в том факте, что restrict определяет указатель, а не данные, на которые он указывает (как это делает const)?

РЕДАКТИРОВАТЬ: Первоначально я полагал, что restrict может иметь последствия для многопоточного кода, но это кажется неправильным, поэтому я удаляю ссылки на потоки из вопроса, чтобы избежать путаницы читателей.

Ответы [ 6 ]

21 голосов
/ 02 октября 2009

Крис Додд имеет правильное описание ключевого слова. На некоторых платформах это может быть очень важно с точки зрения производительности, поскольку позволяет компилятору знать, что, как только он загрузит данные через этот указатель в регистр, он не должен делать это снова. Без этой гарантии компилятор должен перезагружать данные через указатель каждый раз, когда записывается любой другой, возможно, псевдоним-указатель, что может вызвать серьезную остановку конвейера, называемую load-hit-store .

const и restrict - это разные понятия, и это не тот случай, когда const подразумевает restrict. Все, что const говорит о том, что вы не будете писать через этот указатель в рамках этой функции . Указатель const все еще может иметь псевдоним. Например, рассмотрим:

int foo( const int *a, int * b )
{
   *b *= 2;
   return *a + *b; // induces LHS: *a must be read back immediately
                   // after write has cleared the store queue
}

Хотя вы не можете напрямую писать в a в этой функции, было бы вполне законно для вас вызвать foo как:

int x = 3;
foo( &x, &x );  // returns 12

restrict - это другая гарантия: обещание, что a != b во всех звонках на foo().

Я написал о ключевом слове restrict и его влиянии на производительность в длину , а и Майк Актон тоже. Несмотря на то, что мы говорим о конкретном PowerPC по порядку, проблема «загрузка-попадание-хранилище» существует и в x86, но неупорядоченное выполнение x86 затрудняет его изоляцию в профиле.

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

14 голосов
/ 02 октября 2009

Лучшая «интуиция» в отношении ключевого слова restrict заключается в том, что она является гарантией (программистом для компилятора), что в течение времени жизни указателя доступ к памяти через этот указатель будет осуществляться ТОЛЬКО через этот указатель, а не через другой указатель или ссылку или глобальный адрес. Поэтому важно, чтобы указатель был свойством как свойства указателя, так и памяти, связывая их вместе, пока указатель не выйдет из области видимости.

8 голосов
/ 02 октября 2009

Большая часть того, что вы знаете, неверна!

const не гарантирует, что что-то не изменится за спиной компилятора. Все, что он делает, это останавливает вы от записи в это место. Возможно, что-то еще может записать в это место, поэтому компилятор не может предположить, что он постоянен.

Как уже говорили другие, ограничивающий квалификатор - это псевдонимы. На самом деле, во время первого раунда стандартизации C было предложено ключевое слово «noalias». К сожалению, предложение было довольно плохо написано - оно вызвало единственное время, когда Деннис Ричи включился в этот процесс, когда он написал письмо, в котором говорилось что-то о том, что «надо сойтись. Это не открыто для переговоров» «

Само собой разумеется, «noalias» не стал частью C. Когда пришло время попробовать еще раз, предложение было написано достаточно лучше, чтобы ограничение было включено в стандарт - и хотя noalias, вероятно, было бы более значимое название для него, это имя было настолько испорчено, что я сомневаюсь, что кто-нибудь даже подумывал попробовать его использовать.

В любом случае, основная цель restrict - сообщить компилятору, что псевдоним этого элемента не будет. Одной из причин этого является временное сохранение вещей в регистрах. Например, рассмотрим что-то вроде:

void f(int *a, int *b, int *c) { 
    for (int i=0; i<*a; i++)
        *b += c[i];
}

Компилятор действительно хочет поместить i в регистр и загрузить * a в регистр, поэтому, когда приходит время решить, выполнять ли другую итерацию цикла, он просто сравнивает значения в этих регистрах друг с другом , К сожалению, он не может этого сделать - если кто-то, кто использовал эту функцию, был полностью сумасшедшим и вызывал ее с помощью a == b, каждый раз, когда он записывает * b внутри цикла, это новое значение также является значением * a - поэтому он должен читать * a из памяти на каждой итерации цикла, на случай, если тот, кто его вызвал, был совершенно безумным. Использование restrict сообщает компилятору, что он может генерировать код, предполагая, что a и b всегда будут различаться, поэтому запись в * a никогда не изменит * b (или наоборот).

5 голосов
/ 02 октября 2009

Ваше понимание в значительной степени верно. Квалификатор restrict просто утверждает, что данные, к которым обращается такой квалифицированный указатель, являются только , к которым обращается этот точный указатель. Это относится как к чтению, так и к записи.

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

Restrict также несет с собой предупреждение API для людей о том, что данная функция реализована с допущением неизменяемых параметров.

Нет необходимости в блокировке пользователем в том, что касается компилятора. Он только хочет убедиться, что он правильно считывает данные, которые предполагалось быть зашифрованными, с помощью кода компилятор должен был сгенерировать , если нет квалификатора restrict. Добавление restrict освобождает его от этой проблемы.

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

Мы можем видеть restrict в действии:

void move(int *a, int *b) {     void move(int *__restrict a, int *__restrict b) {
    a[0] = b[0];                    a[0] = b[0];
    a[1] = b[0];                    a[1] = b[0];
}                               }
    movl    (%edx), %eax            movl    (%edx), %edx
    movl    %eax, (%ecx)            movl    %edx, (%eax)
    movl    (%edx), %eax            movl    %edx, 4(%eax)
    movl    %eax, 4(%ecx)

В правом столбце, с restrict, компилятору не нужно было перечитывать b[0] из памяти. Он смог прочитать b[0] и сохранить его в регистре %edx, а затем просто дважды сохранить регистр в памяти. В левом столбце он не знал, изменился ли магазин на a b.

1 голос
/ 02 октября 2009

Это может быть пример из чрезвычайно узкого домена, но платформа Altera Nios II представляет собой программный микроконтроллер с мягким ядром, который можно настраивать в FPGA. Затем, в исходном коде C для этого микро, вы можете использовать инструмент C-to-hardware для ускорения внутренних циклов, используя нестандартное оборудование, а не программное обеспечение.

Здесь использование ключевого слова __restrict__ (которое совпадает с restrict в C99) позволяет инструменту C2H правильно оптимизировать аппаратное ускорение операции указателя параллельно вместо последовательного. По крайней мере, в этом случае restrict просто не предназначен для потребления человеком. Смотрите также Страница Солнца на restrict, где в первой строке написано

Использование квалификатора restrict надлежащим образом в программах на Си может позволить компилятору создавать значительно более быстрые исполняемые файлы.

Если кому-то интересно больше узнать о C2H, в этом PDF обсуждается оптимизация результатов C2H. Раздел на __restrict__ находится на странице 20.

1 голос
/ 02 октября 2009

Кто-то, более знакомый со стандартом, возможно, даст лучший ответ, но я дам ему шанс.

«Данные не будут изменены за спиной компилятора», для меня это звучит как противоположность «volatile».

«const» означает, что данные не будут изменены перед программистом; то есть она не может изменить данные с помощью обозначения, помеченного как «const» (я пишу «обозначение», потому что в int const *pi имя pi не является const, а *pi есть). Данные могут быть изменены с помощью другого обозначения (в конце концов, неконстантные данные могут быть переданы в функцию как константные данные).

Ключ ограничивает «ограничить» указатели. Указатели - это единственный способ псевдонимов данных в C, так что это единственный способ получить доступ к некоторому фрагменту данных через два разных имени. «restrict» - это ограничение доступа к данным одним путем доступа.

...