Ограничивает ли помощь в C, если указатель уже помечен как const?
Просто интересно: когда я добавляю restrict к указателю, я говорю компилятору, что указатель не является псевдонимом для другого указателя. Давайте предположим, что у меня есть такая функция:
// Constructed example
void foo (float* result, const float* a, const float* b, const size_t size)
{
for (size_t i = 0; i < size; ++i)
{
result [i] = a [0] * b [i];
}
}
Если компилятор должен предполагать, что result
может пересекаться с a
, он должен каждый раз перевыпускать. Но, как a
отмечен const
Компилятор также может предположить, что a является фиксированным, и, следовательно, получить его один раз можно.
Вопрос в такой ситуации, каков рекомендуемый способ работы с ограничением? Я, конечно, не хочу, чтобы компилятор повторно a
каждый раз, но я не мог найти хорошую информацию о том, как restrict
должен работать здесь.
5 ответов
Ваш указатель является константой, сообщая любому, кто вызывает вашу функцию, что вы не будете касаться данных, на которые указывает эта переменная. К сожалению, компилятор все еще не будет знать, является ли результат псевдонимом константных указателей. Вы всегда можете использовать неконстантный указатель в качестве константного указателя. Например, многие функции принимают в качестве параметра указатель const char (т.е. string), но вы можете, если хотите, передать ему неконстантный указатель, функция просто дает вам обещание, что она не будет использовать этот конкретный указатель, чтобы изменить что-либо.
По сути, чтобы приблизиться к вашему вопросу, вам нужно добавить ограничение a и b, чтобы "пообещать" компилятору, что тот, кто использует эту функцию, не передаст в качестве псевдонима a или b. Предполагая, конечно, вы можете дать такое обещание.
Да, вам нужно ограничиться. Pointer-to-const не означает, что ничто не может изменить данные, только то, что вы не можете изменить их через этот указатель.
const
В основном это просто механизм запроса компилятора, который поможет вам отслеживать, какие вещи вы хотите, чтобы функции могли быть изменены. const
это не обещание компилятору, что функция действительно не будет изменять данные.
В отличие от restrict
используя указатель на const
изменяемые данные в основном обещают другим людям, а не компилятору. Отбрасывание const
повсюду не приведет к неправильному поведению оптимизатора (AFAIK), если только вы не попытаетесь изменить то, что компилятор поместил в постоянную память (см. ниже о static const
переменные). Если компилятор не может видеть определение функции при оптимизации, он должен предположить, что он отбрасывает const
и изменяет данные через этот указатель (то есть, что функция не уважает const
его аргументы указателя).
Компилятор знает, что static const int foo = 15;
однако, не может измениться и надежно встроит значение, даже если вы передадите его адрес неизвестным функциям. (Вот почему static const int foo = 15;
не медленнее, чем #define foo 15
для оптимизирующего компилятора. Хорошие компиляторы будут оптимизировать его как constexpr
когда возможно.)
Помни что restrict
это обещание компилятору, что вещи, к которым вы обращаетесь через этот указатель, не пересекаются ни с чем другим. Если это не так, ваша функция не обязательно будет делать то, что вы ожидаете. например, не звоните foo_restrict(buf, buf, buf)
работать на месте.
По моему опыту (с gcc и clang), restrict
в основном полезен для указателей, через которые вы сохраняете. Не больно ставить restrict
в ваших исходных указателях тоже, но обычно вы получаете все возможные улучшения asm, помещая его только в целевой указатель (и), если все хранилища, которые выполняет ваша функция, проходят через restrict
указатели.
Если у вас есть какие-либо вызовы функций в вашем цикле, restrict
указатель на источник позволяет clang (но не gcc) избежать перезагрузки. Посмотрите эти тестовые примеры в проводнике компилятора Godbolt, а именно этот:
void value_only(int); // a function the compiler can't inline
int arg_pointer_valonly(const int *__restrict__ src)
{
// the compiler needs to load `*src` to pass it as a function arg
value_only(*src);
// and then needs it again here to calculate the return value
return 5 + *src; // clang: no reload because of __restrict__
}
gcc6.3 (нацеленный на x86-64 SysV ABI) решает сохранить src
(указатель) в регистре с сохранением вызова через вызов функции и перезагрузите *src
после звонка. Либо алгоритмы gcc не обнаружили такой возможности оптимизации, либо решили, что она того не стоит, либо разработчики gcc специально не реализовали ее, потому что считают, что это небезопасно. ИДК который. Но поскольку clang делает это, я предполагаю, что это, вероятно, законно в соответствии со стандартом C11.
clang4.0 оптимизирует это только для загрузки *src
один раз, и сохраните значение в регистре с сохранением вызова через вызов функции. Без restrict
, он этого не делает, потому что вызываемая функция может (как побочный эффект) изменить *src
через другой указатель.
Например, вызывающая сторона этой функции могла передать адрес глобальной переменной. Но любая модификация *src
кроме как через src
указатель будет нарушать обещание, что restrict
сделал компилятору. Так как мы не передаем src
в valonly()
компилятор может предположить, что он не изменяет значение.
GNU диалект C позволяет использовать __attribute__((pure))
или же __attribute__((const))
объявить, что функция не имеет побочных эффектов, что позволяет эту оптимизацию без restrict
, но в ISO C11 (AFAIK) нет переносимого эквивалента. Разумеется, разрешение встроенной функции (путем помещения ее в заголовочный файл или использования LTO) также позволяет оптимизировать этот вид, и это намного лучше для небольших функций, особенно если они вызываются внутри циклов.
Компиляторы, как правило, довольно агрессивны в выполнении оптимизаций, которые допускает стандарт, даже если они удивляют некоторых программистов и ломают некоторый существующий небезопасный код, который сработал. (C настолько переносим, что многие вещи являются неопределенным поведением в базовом стандарте; большинство хороших реализаций определяют поведение многих вещей, которые стандарт оставляет как UB.) C не является языком, в котором безопасно бросать код в компилятор до он делает то, что вы хотите, не проверяя, что вы делаете это правильно (без переполнения со знаком и т. д.)
Если вы посмотрите на вывод x86-64 asm для компиляции вашей функции (из вопроса), вы можете легко увидеть разницу. Я положил его на проводник компилятора Godbolt.
В этом случае положить restrict
на a
достаточно, чтобы лязг поднять груз a[0]
, но не GCC.
С float *restrict result
, и clang, и gcc поднимут груз.
например
# gcc6.3, for foo with no restrict, or with just const float *restrict a
.L5:
vmovss xmm0, DWORD PTR [rsi]
vmulss xmm0, xmm0, DWORD PTR [rdx+rax*4]
vmovss DWORD PTR [rdi+rax*4], xmm0
add rax, 1
cmp rcx, rax
jne .L5
против
# gcc 6.3 with float *__restrict__ result
# clang is similar with const float *__restrict__ a but not on result.
vmovss xmm1, DWORD PTR [rsi] # outside the loop
.L11:
vmulss xmm0, xmm1, DWORD PTR [rdx+rax*4]
vmovss DWORD PTR [rdi+rax*4], xmm0
add rax, 1
cmp rcx, rax
jne .L11
Итак, в итоге, поставить __restrict__
на всех указателях, которые гарантированно не пересекаются с чем-то другим.
КСТАТИ, restrict
это только ключевое слово в C. Некоторые компиляторы C++ поддерживают __restrict__
или же __restrict
как расширение, так что вы должны #ifdef
это прочь на неизвестных компиляторах.
поскольку
Все здесь кажутся очень смущенными. Пока ни в одном ответе нет ни одного примера константного указателя.
Декларация const float* a
это не константный указатель, это константное хранилище. Указатель все еще изменчив. float *const a
константный указатель на изменяемый тип с плавающей точкой
Так что вопрос должен быть, есть ли смысл в float *const restrict a
(или же const float *const restrict a
Если вы предпочитаете).
В стандарте C-99 (ISO/IEC 9899:1999 (E)) приведены примеры const * restrict
например, в разделе 7.8.2.3:
Функции strtoimax и strtoumax
конспект
#include <inttypes.h>
intmax_t strtoimax(const char * restrict nptr,
char ** restrict endptr, int base);
--- snip ---
Поэтому, если предположить, что стандарт не предоставит такой пример, если const *
были избыточны для * restrict
тогда они действительно не являются лишними.
Как говорилось в предыдущем ответе, вам нужно добавить "ограничение". Я также хотел прокомментировать ваш сценарий, что "результат может совпадать с". Это не единственная причина, по которой компилятор обнаружит, что "а" может измениться. Он также может быть изменен другим потоком с указателем на "а". Таким образом, даже если ваша функция не изменила никаких значений, компилятор все равно будет предполагать, что "a" может измениться.