Ограничивает ли помощь в C, если указатель уже помечен как const?

StackOverflow https://stackoverflow.com/questions/457441

Вопрос

Просто интересуюсь:Когда я добавляю ограничение к указателю, я сообщаю компилятору, что этот указатель не является псевдонимом другого указателя.Предположим, у меня есть такая функция:

// 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 здесь должен работать.

Это было полезно?

Решение

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

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

Другие советы

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

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

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

Да, вам нужно ограничить. Указатель на константу не означает, что ничто не может изменить данные, а только то, что вы не можете изменить их с помощью этого указателя..

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

В отличие от restrict, используя указатель на-const использование изменяемых данных — это, по сути, обещание другим людям, а не компилятору.Отбрасывание const повсюду не приведет к неправильному поведению оптимизатора (AFAIK), если только вы не попытаетесь изменить что-то, что компилятор поместил в постоянную память (см. ниже static const переменные).Если компилятор не видит определения функции при оптимизации, он должен предположить, что она отбрасывает const и изменяет данные через этот указатель (т.е.что функция не учитывает constзначимость его указателя args).

Компилятор это знает 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(), компилятор может предположить, что он не изменяет значение.

Диалект C GNU позволяет использовать __attribute__((pure)) или __attribute__((const)) объявить, что функция не имеет побочных эффектов, что позволяет выполнить эту оптимизацию без restrict, но в ISO C11 (AFAIK) нет портативного эквивалента.Конечно, встраивание функции (путем помещения ее в заголовочный файл или использование LTO) также допускает подобную оптимизацию и гораздо лучше подходит для небольших функций, особенно если они вызываются внутри циклов.


Компиляторы, как правило, довольно агрессивно подходят к оптимизации, допускаемой стандартом, даже если она удивляет некоторых программистов и нарушает существующий небезопасный код, который работал.(C настолько переносим, ​​что поведение многих вещей в базовом стандарте не определено;самые хорошие реализации действительно определяют поведение многих вещей, которые стандарт оставляет как UB.) C не является языком, в котором можно безопасно бросать код в компилятор, пока он не сделает то, что вы хотите, не проверяя, что вы делаете это правильно способ (без переполнения целых чисел со знаком и т. д.)


Если вы посмотрите на выходные данные asm x86-64 для компиляции вашей функции (из вопроса), вы легко увидите разницу.я надел это обозреватель компилятора 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 это пропало на неизвестных компиляторах.

С

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top