Да, вам нужно ограничить. Pointer-to-const не означает, что ничто не может изменить данные, только то, что вы не можете изменить их через этот указатель .
const
- это, в основном, механизм, который просит компилятор помочь вам отслеживать, какие вещи вы хотите, чтобы функции могли быть изменены. const
не обещает компилятору, что функция действительно не будет изменять данные .
В отличие от restrict
, использование указателя на const
для изменяемых данных в основном обещает другим людям, а не компилятору. Сбрасывание const
повсюду не приведет к неправильному поведению оптимизатора (AFAIK), если вы не попытаетесь изменить что-то, что компилятор поместил в постоянную память (см. Ниже о static const
переменных). Если компилятор не может увидеть определение функции при оптимизации, он должен предположить, что он отбрасывает const
и изменяет данные через этот указатель (то есть, что функция не учитывает const
ness своих аргументов указателя ).
Компилятор знает, что 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, помещая его только в указатель (и) назначения, если all сохраняет вашу функцию делает через 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 нет переносимого эквивалента (НАСКОЛЬКО МНЕ ИЗВЕСТНО). Разумеется, разрешение встроенной функции (путем помещения ее в заголовочный файл или использования LTO) также позволяет оптимизировать этот вид, и это намного лучше для небольших функций, особенно если они вызываются внутри циклов.
Компиляторы, как правило, довольно агрессивны в выполнении оптимизаций, которые допускает стандарт, даже если они удивляют некоторых программистов и ломают некоторый существующий небезопасный код, который сработал. (C настолько переносим, что многие вещи являются неопределенным поведением в базовом стандарте; большинство хороших реализаций определяют поведение многих вещей, которые стандарт оставляет как UB.) C не является языком, в котором безопасно бросать код в компилятор до он делает то, что вы хотите, без проверки, что вы делаете это правильно (без переполнения со знаком и целыми числами и т. д.)
Если вы посмотрите на вывод asm x86-64 для компиляции вашей функции (из вопроса), вы можете легко увидеть разницу. Я поставил его на проводник компилятора Godbolt .
В этом случае достаточно поставить restrict
на a
, чтобы лязг мог поднять груз a[0]
, но не gcc.
При float *restrict result
и clang, и gcc поднимут груз.
* 1 089 *, например
# 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
удалить его на неизвестных компиляторах.
С