Я пытаюсь понять когда и если не использовать restrict
ключевое слово в C и в том, какие ситуации это предоставляет материальное преимущество.
После чтения, "Демистифицируя Ограничить Ключевое слово", (который обеспечивает некоторые эмпирические правила на использовании), я получаю впечатление, что, когда функция является переданными указателями, она должна составлять возможность, что данные указали, мог бы наложиться (искажают) с любыми другими аргументами, передаваемыми в функцию. Учитывая функцию:
foo(int *a, int *b, int *c, int n) {
for (int i = 0; i
компилятор должен перезагрузить c
во втором выражении, потому что, возможно, b
и c
укажите на то же местоположение. Это также должно ожидать b
быть сохраненным, прежде чем это сможет загрузиться a
по той же причине. Это затем должно ожидать a
быть сохраненным и должно перезагрузить b
и c
в начале следующего цикла. Если Вы вызываете функцию как это:
int a[N];
foo(a, a, a, N);
затем Вы видите, почему компилятор должен сделать это. Используя restrict
эффективно говорит компилятору, что Вы никогда не будете делать этого, так, чтобы он мог отбросить избыточную загрузку c
и загрузка a
прежде b
хранится.
До сих пор я заключил, что это - хорошая идея использовать restrict
на указателях Вы передаете в функции, которые не будут встроены. По-видимому, если код встраивается, компилятор может выяснить, что указатели не накладываются.
Теперь вот то, где вещи начинают становиться нечеткими для меня.
В статье Ulrich Drepper, "Что каждый программист должен знать о памяти", что делает оператор, что, "если не ограничивают, используется, все доступы указателя являются потенциальными источниками искажения", и он дает определенный пример кода субматрицы, умножение матриц, где он использует 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
#include
#include
#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;
}
Это намек на оптимизатор кода. Использование ограничения позволяет ему хранить переменную указателя в регистре CPU и не производить обновление значения указателя в память для обновления псевдонима.
Воспользуется ли оптимизатор этим или нет, во многом зависит от особенностей реализации оптимизатора и процессора. Оптимизаторы кода уже вложили значительные средства в обнаружение несглаживания, так как это такая важная оптимизация. Обнаружить это в своем коде не составит труда
.] Может быть, оптимизация, проведенная здесь, не зависит от того, что указатели не являются псевдонимами? Если только вы не загрузите несколько mul2-элементов перед записью результата в res2, я не вижу никакой проблемы с псевдонимом.[
]. [] В первом куске кода, который вы показываете, вполне понятно, с какими псевдонимами может возникнуть проблема. Здесь не так уж и ясно. [
] [] Перечитывая статью Dreppers, он конкретно не говорит, что ограничение может решить что угодно. Есть даже такая фраза :[
] [] [] [] {В теории ключевое слово ограничения введённый в язык C Пересмотр 1999 года должен решить проблему проблема. Составители не наверстали Тем не менее. Причина в том, что слишком много некорректного кода, который ввел бы в заблуждение компилятор и вызвал бы чтобы сгенерировать некорректный объектный код.}[
] [
] В данном коде оптимизация доступа к памяти уже выполнена в рамках алгоритма. Остаточная оптимизация, судя по всему, выполнена в векторном коде, представленном в приложении. Так что для представленного здесь кода никакой разницы, наверное, нет, так как оптимизация, основанная на ограничении, не производится. Каждый доступ к указателю является источником псевдонимы, но не каждая оптимизация полагается на псевдонимы.[
]. []Преждевременная оптимизация, будучи корнем всего зла, использование ограничительного ключевого слова должно быть ограничено тем случаем, когда вы активно изучаете и оптимизируете, а не использоваться везде, где оно может быть использовано.[
]] Ты работаешь на 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[
], чтобы проверить, удается ли ему векторизовать цикл или нет; это простой способ проверить эффект от добавления ключевого слова limit.[
Если есть разница вообще, переместите mm
в отдельный DSO (так что gcc больше не может знать все о вызывающем коде) будет способом продемонстрировать это.
Кроме того, в GCC 4.0.0-4.4 есть ошибка регрессии, из-за которой ключевое слово restrict игнорируется. Сообщалось, что эта ошибка исправлена в версии 4.5 (хотя номер ошибки я потерял).