Что могут люди сделать из ограничительного квалификатора?
Если я получил C99 restrict
ключевое слово right, указание на него указателя - это обещание, что данные, на которые он ссылается, не будут изменены за спиной компилятора через псевдонимы.
В отличие от того, как я понимаю const
Спецификатор - это документация, поддерживаемая компилятором, что данный объект не будет изменен за спиной человека, пишущего код. Компилятор может получить подсказку как побочный эффект, но мне как программисту все равно.
Аналогичным образом, было бы целесообразно рассмотреть restrict
квалификатор в прототипе функции как требование, чтобы пользователь гарантировал исключительный доступ ("избегайте псевдонимов", или, может быть, что-то более сильное) на время вызова? Должно ли оно использоваться как "документация"?
Кроме того, есть ли что-то понять в том, что restrict
квалифицирует указатель, а не данные, на которые он указывает (как const
делает)?
РЕДАКТИРОВАТЬ: я изначально верил, что restrict
может иметь последствия для многопоточного кода, но это кажется неправильным, поэтому я убираю ссылки на темы из вопроса, чтобы не запутать читателей.
6 ответов
Лучшая "интуиция" для ключевого слова restrict - гарантия (программистом компилятору), что в течение времени жизни указателя доступ к памяти через этот указатель будет осуществляться ТОЛЬКО через этот указатель, а не через другой указатель. или ссылка или глобальный адрес. Поэтому важно, чтобы указатель был свойством как свойства указателя, так и памяти, связывая их вместе, пока указатель не выйдет из области видимости.
Крис Додд имеет правильное описание ключевого слова. На некоторых платформах это может быть очень важно с точки зрения производительности, поскольку позволяет компилятору знать, что после загрузки данных через этот указатель в регистр ему больше не нужно это делать. Без этой гарантии компилятор должен перезагружать данные через указатель каждый раз, когда записывается любой другой, возможно, псевдоним-указатель, что может вызвать серьезную остановку конвейера, называемую хранилищем загрузки-попадания.
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
может привести к действительно значительным ускорениям при правильном использовании.
Большая часть того, что вы знаете, неверна!
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 (или наоборот).
Ваше понимание в значительной степени правильно. 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
,
Это может быть примером из очень узкой области, но платформа Altera Nios II - это микроконтроллер с мягким ядром, который можно настраивать в FPGA. Затем, в исходном коде C для этого микро, вы можете использовать инструмент C-to-hardware для ускорения внутренних циклов, используя нестандартное оборудование, а не программное обеспечение.
Там, использование __restrict__
Ключевое слово (которое совпадает с C99 restrict
) позволяет инструменту C2H правильно оптимизировать аппаратное ускорение работы указателя параллельно, а не последовательно. По крайней мере, в этом случае restrict
просто не предназначен для потребления человеком. Смотрите также страницу Sun наrestrict
где первая строка говорит
С использованием
restrict
Соответствующий квалификатор в программах на Си может позволить компилятору создавать значительно более быстрые исполняемые файлы.
Если кому-то интересно узнать больше о C2H, этот PDF-файл обсуждает оптимизацию результатов C2H. Раздел о __restrict__
находится на странице 20.
Кто-то, более знакомый со стандартом, мог бы дать лучший ответ, но я попробую.
"Данные не будут изменены за спиной компилятора", для меня это звучит как противоположность "volatile".
"const" означает, что данные не будут изменены перед программистом; то есть она не может изменить данные с помощью обозначения, помеченного как "const" (я пишу "обозначение", потому что в int const *pi
, имя pi
не постоянный, но *pi
является). Данные могут быть изменены с помощью другого обозначения (в конце концов, неконстантные данные могут быть переданы в функцию как константные данные).
Это "ограничение" квалифицирует указатели является ключом. Указатели являются единственным способом псевдонимов данных в C, поэтому они являются единственным способом, которым вы можете получить доступ к некоторому фрагменту данных через два разных имени. "restrict" - это ограничение доступа к данным одним путем доступа.