Правила использования ключевого слова restrict в C? - PullRequest
68 голосов
/ 05 января 2010

Я пытаюсь понять, когда и когда не следует использовать ключевое слово 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 Нильс Пипенбринк приводит рабочий пример этого сценария, демонстрирующий выигрыш в производительности.

Пока я понял, что это хорошая идея - использовать 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;
}

Ответы [ 8 ]

14 голосов
/ 24 августа 2010

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

13 голосов
/ 05 января 2010

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

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

3 голосов
/ 16 сентября 2013

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

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

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

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

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

3 голосов
/ 25 января 2011

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

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

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

1 голос
/ 24 августа 2010

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

1 голос
/ 05 января 2010

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

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

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

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

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

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

0 голосов
/ 05 сентября 2015

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

0 голосов
/ 05 января 2010

Работаете ли вы на 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...