Ограничивает ли помощь в C, если указатель уже помечен как const? - PullRequest
22 голосов
/ 19 января 2009

Просто интересно: когда я добавляю 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 ]

15 голосов
/ 19 января 2009

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

По сути, чтобы приблизиться к вашему вопросу, вам нужно добавить ограничение a и b, чтобы «пообещать» компилятору, что тот, кто использует эту функцию, не передаст в качестве псевдонима a или b. Предполагая, конечно, что вы можете дать такое обещание.

8 голосов
/ 28 апреля 2017

Да, вам нужно ограничить. 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 удалить его на неизвестных компиляторах.

С

8 голосов
/ 25 октября 2013

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

Объявление const float* a это не константный указатель, это константное хранилище. Указатель все еще изменчив. float *const a - это константный указатель на изменяемое число с плавающей точкой.

Так что вопрос должен быть, есть ли смысл в float *const restrict a (или const float *const restrict a, если вы предпочитаете).

7 голосов
/ 25 августа 2011

В стандарте 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, то они действительно не являются избыточными.

1 голос
/ 26 мая 2009

Как говорилось в предыдущем ответе, вам нужно добавить «restrict». Я также хотел прокомментировать ваш сценарий, что «результат может совпадать с». Это не единственная причина, по которой компилятор обнаружит, что «а» может измениться. Он также может быть изменен другим потоком, который имеет указатель на «а». Таким образом, даже если ваша функция не изменила никаких значений, компилятор все равно будет предполагать, что «a» может измениться.

...