Правила использования ключевого слова restrict в C?

Я пытаюсь понять, когда и когда не следует использовать ключевое слово restrict в C и в каких ситуациях оно дает ощутимые преимущества .

После прочтения " демистификация ключевого слова Restrict ", (которая предоставляет некоторые практические правила использования), у меня складывается впечатление, что когда функция передается указателям, она должна учитывать возможность того, что данные, на которые указывают, могут перекрываться (псевдоним) с любыми другими аргументами, являющимися перешел в функцию. Дана функция:

foo(int *a, int *b, int *c, int n) {
    for (int i = 0; i<n; ++i) {
        b[i] = b[i] + c[i];
        a[i] = a[i] + b[i] * c[i];
    } 
}

компилятор должен перезагрузить c во втором выражении, потому что, возможно, b и c указывают на одно и то же местоположение. Также необходимо дождаться, пока b будет сохранено, прежде чем он сможет загрузить a по той же причине. Затем он должен дождаться сохранения a и перезагрузить b и c в начале следующего цикла. Если вы вызываете функцию следующим образом:

int a[N];
foo(a, a, a, N);

тогда вы можете понять, почему компилятор должен это делать. Использование restrict фактически говорит компилятору, что вы никогда этого не сделаете, так что он может сбросить избыточную загрузку c и загрузить a перед b сохраняется.

В другой пост SO, Nils Pipenbrinck, предоставляет рабочий пример этого сценария, демонстрирующий выигрыш в производительности.

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

Теперь здесь все становится неясным для меня.

В статье Ульриха Дреппера " Что каждый программист должен знать о памяти ", он делает заявление что «если не используется ограничение, все обращения к указателям являются потенциальными источниками алиасинга», и он приводит конкретный пример кода умножения матрицы подматрицы, где он использует restrict.

Однако, когда я компилирую его пример кода с или без restrict, я получаю идентичные двоичные файлы в обоих случаях. Я использую gcc version 4.2.4 (Ubuntu 4.2.4-1ubuntu4)

В следующем коде я не могу понять, нужно ли его переписать, чтобы более широко использовать restrict или если анализ псевдонимов в GCC настолько хорош, что он может выяснить, что ни один из аргументов не является псевдонимом друг друга. В чисто образовательных целях, как я могу заставить использование или не использовать restrict в этом коде - и почему?

Для restrict, скомпилированного с:

gcc -DCLS=$(getconf LEVEL1_DCACHE_LINESIZE) -DUSE_RESTRICT -Wextra -std=c99 -O3 matrixMul.c -o matrixMul

Просто удалите -DUSE_RESTRICT, чтобы не использовать restrict.

#include <stdlib.h>
#include <stdio.h>
#include <emmintrin.h>

#ifdef USE_RESTRICT
#else
#define restrict
#endif

#define N 1000
double _res[N][N] __attribute__ ((aligned (64)));
double _mul1[N][N] __attribute__ ((aligned (64)))
    = { [0 ... (N-1)] 
    = { [0 ... (N-1)] = 1.1f }};
double _mul2[N][N] __attribute__ ((aligned (64)))
    = { [0 ... (N-1)] 
    = { [0 ... (N-1)] = 2.2f }};

#define SM (CLS / sizeof (double))

void mm(double (* restrict res)[N], double (* restrict mul1)[N], 
        double (* restrict mul2)[N]) __attribute__ ((noinline));

void mm(double (* restrict res)[N], double (* restrict mul1)[N], 
        double (* restrict mul2)[N])
{
 int i, i2, j, j2, k, k2; 
    double *restrict rres; 
    double *restrict rmul1; 
    double *restrict rmul2; 

    for (i = 0; i < N; i += SM)
        for (j = 0; j < N; j += SM)
            for (k = 0; k < N; k += SM)
                for (i2 = 0, rres = &res[i][j],
                    rmul1 = &mul1[i][k]; i2 < SM;
                    ++i2, rres += N, rmul1 += N)
                    for (k2 = 0, rmul2 = &mul2[k][j];
                        k2 < SM; ++k2, rmul2 += N)
                        for (j2 = 0; j2 < SM; ++j2)
                          rres[j2] += rmul1[k2] * rmul2[j2];
}

int main (void)
{

    mm(_res, _mul1, _mul2);

 return 0;
}
67 голосов | спросил Robert S. Barnes 5 Jpm1000000pmTue, 05 Jan 2010 13:51:28 +030010 2010, 13:51:28

8 ответов


0

Кроме того, в GCC 4.0.0-4.4 имеется ошибка регрессии, из-за которой ключевое слово restrict игнорируется. Эта ошибка была исправлена ​​в 4.5 (хотя я потерял номер ошибки).

ответил Fredrik Berg Kjolstad 24 AM00000060000003331 2010, 06:23:33
0

Это подсказка оптимизатору кода. Использование restrict гарантирует, что он может хранить переменную указателя в регистре ЦП и не должен сбрасывать обновление значения указателя в память, так что псевдоним также обновляется.

Использует ли он преимущества или нет, в значительной степени зависит от деталей реализации оптимизатора и процессора. Оптимизаторы кода уже вложили значительные средства в обнаружение сглаживания, поскольку это такая важная оптимизация. Не должно быть проблем с обнаружением этого в вашем коде.

ответил Hans Passant 5 Jpm1000000pmTue, 05 Jan 2010 16:05:22 +030010 2010, 16:05:22
0

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

Когда вы знаете, что указатель A является единственным указателем на некоторую область памяти, то есть у него нет псевдонимов (то есть любой другой указатель B обязательно будет не равен A, B! = A), Вы можете сообщить об этом оптимизатору, указав тип A с помощью ключевого слова restrict.

Я написал об этом здесь: http://mathdev.org/node/23 и попытался чтобы показать, что некоторые ограниченные указатели на самом деле являются «линейными» (как упоминалось в этом посте).

ответил Artyom Shalkhakov 25 Jpm1000000pmTue, 25 Jan 2011 19:38:59 +030011 2011, 19:38:59
0

Стоит отметить, что последние версии clang способны генерировать код с проверкой псевдонимов во время выполнения и двумя путями кода : один для случаев, когда существует потенциальный псевдоним, и другой для случая, где есть очевидный, нет шансов на это.

Это явно зависит от экстентов данных, указывающих на то, что они заметны для компилятора - как это было бы в примере выше.

Я считаю, что главное оправдание - программы, интенсивно использующие STL - и особенно <algorithm>, где сложно или невозможно ввести квалификатор __restrict.

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

Я был бы удивлен, если бы GCC также не получил эту оптимизацию.

ответил marko 16 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowMon, 16 Sep 2013 02:09:58 +0400 2013, 02:09:58
0

Может быть, проведенная здесь оптимизация не полагается на то, что указатели не будут псевдонимами? Если вы предварительно не загрузите несколько элементов mul2 перед записью результата в res2, я не вижу никакой проблемы с алиасами.

В первом фрагменте кода, который вы показываете, совершенно ясно, какая проблема с псевдонимами может возникнуть. Здесь это не так понятно.

Перечитывая статью Dreppers, он специально не говорит, что ограничение может что-то решить. Есть даже эта фраза:

  

{В теории ключевое слово restrict   введен в язык C в   Пересмотр 1999 года должен решить   проблема. Компиляторы не догнали   все же, все же. Причина в основном в том, что   существует слишком много неверного кода, который   может ввести в заблуждение компилятор и вызвать   это генерировать неправильный объектный код.}

В этом коде оптимизация доступа к памяти уже выполнена в алгоритме. По-видимому, остаточная оптимизация выполняется в векторизованном коде, представленном в приложении. Так что для кода, представленного здесь, я думаю, что нет никакой разницы, потому что никакая оптимизация, опирающаяся на ограничение, не выполняется. Каждый доступ к указателю является источником псевдонимов, но не каждая оптимизация основывается на псевдонимах.

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

ответил shodanex 5 Jpm1000000pmTue, 05 Jan 2010 14:46:52 +030010 2010, 14:46:52
0

Если вообще есть разница, переместите mm в отдельный DSO (чтобы gcc больше не мог знать все о вызывающем код) будет способ продемонстрировать это.

ответил Logan Capaldo 24 AM00000060000002031 2010, 06:30:20
0

Работаете ли вы на 32- или 64-разрядной версии Ubuntu? Если 32-битный, то вам нужно добавить -march=core2 -mfpmath=sse (или какова бы ни была ваша архитектура процессора), иначе он не использует SSE. Во-вторых, чтобы включить векторизацию в GCC 4.2, вам нужно добавить опцию -ftree-vectorize (для 4.3 или 4.4 это включено по умолчанию). в -O3). Также может потребоваться добавить -ffast-math (или другую опцию, обеспечивающую упрощенную семантику с плавающей запятой), чтобы позволить компилятору переупорядочивать операции с плавающей запятой .

Кроме того, добавьте параметр -ftree-vectorizer-verbose=1, чтобы увидеть, удастся ли ему векторизовать цикл или нет; это простой способ проверить эффект добавления ключевого слова restrict.

ответил janneb 5 Jpm1000000pmTue, 05 Jan 2010 16:55:56 +030010 2010, 16:55:56
0

Проблема с вашим примером кода состоит в том, что компилятор просто вставит вызов и увидит, что в вашем примере нет возможности использовать псевдонимы. Я предлагаю вам удалить функцию main () и скомпилировать ее, используя -c.

ответил Kurt Roeckx 5 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowSat, 05 Sep 2015 23:50:42 +0300 2015, 23:50:42

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132