Где здесь петлевая зависимость?

Кто-нибудь видит что-нибудь очевидное в коде цикла ниже, чего я не вижу, почему это не может быть автоматически векторизовано компилятором С++ VS2012?

Все, что компилятор дает мне, это info C5002: loop not vectorized due to reason '1200', когда я использую переключатель командной строки /Qvec-report:2.

Причина 1200 задокументирована в MSDN как:

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

Я знаю (или я почти уверен, что) нет никаких зависимостей данных, переносимых циклом, но я не уверен, что мешает компилятору понять это.

Эти указатели source и dest никогда не перекрываются и не создают псевдонимы одной и той же памяти, и я пытаюсь предоставить компилятору эту подсказку через __restrict.

pitch всегда является положительным целым числом, что-то вроде 4096, в зависимости от разрешения экрана, поскольку это функция рендеринга/преобразования 8bpp->32bpp, работающая по столбцам.

byte  * __restrict source;
DWORD * __restrict dest;
int pitch;

for (int i = 0; i < count; ++i) {
    dest[(i*2*pitch)+0] = (source[(i*8)+0]);
    dest[(i*2*pitch)+1] = (source[(i*8)+1]);
    dest[(i*2*pitch)+2] = (source[(i*8)+2]);
    dest[(i*2*pitch)+3] = (source[(i*8)+3]);

    dest[((i*2+1)*pitch)+0] = (source[(i*8)+4]);
    dest[((i*2+1)*pitch)+1] = (source[(i*8)+5]);
    dest[((i*2+1)*pitch)+2] = (source[(i*8)+6]);
    dest[((i*2+1)*pitch)+3] = (source[(i*8)+7]);
}

Круглые скобки вокруг каждого source[] являются остатками вызова функции, который я здесь опустил, потому что цикл по-прежнему не будет автоматически векторизован без вызова функции в его самой простой форме.

ИЗМЕНИТЬ:

Я упростил цикл до его самой тривиальной формы:

for (int i = 0; i < 200; ++i) {
    dest[(i*2*4096)+0] = (source[(i*8)+0]);
}

Это по-прежнему производит тот же код причины 1200.

ИЗМЕНИТЬ (2):

Этот минимальный тестовый пример с локальными выделениями и идентичными типами указателей по-прежнему не может автоматически векторизоваться. Я просто сбит с толку в этот момент.

const byte * __restrict source;
byte * __restrict dest;
source = (const byte * __restrict ) new byte[1600];
dest = (byte * __restrict ) new byte[1600];
for (int i = 0; i < 200; ++i) {
    dest[(i*2*4096)+0] = (source[(i*8)+0]);
}

person James Dunne    schedule 23.12.2012    source источник
comment
pitch всегда является положительным целым числом. Но знает ли об этом компилятор?   -  person Mysticial    schedule 23.12.2012
comment
Я просто заменил его литералом; такой же результат. Я также удалил все, кроме первой строки цикла, чтобы получить тривиальный случай; такой же результат.   -  person James Dunne    schedule 23.12.2012
comment
Я считаю, что VC++ обрабатывает только restrict на уровне функций. Таким образом, объявление указателей как ограниченных внутри функции ничего не делает. Попробуйте заменить их выделениями. Может быть, будет достаточно ума, чтобы признать, что они не могут быть псевдонимами, если они поступают из разных распределений.   -  person Mysticial    schedule 23.12.2012
comment
Локальные переменные-указатели функции назначаются из глобальных переменных (устаревшая кодовая база - не вините меня: P). Хорошее мышление там; Я посмотрю, смогу ли я обойтись без пощечины __restrict на глобальных переменных, и если это не удастся, я попробую просто с фиктивными выделениями в функции.   -  person James Dunne    schedule 23.12.2012
comment
Как только я удалил выражения из индексаторов dest[i] = source[i];, это дало мне причину 1300: тело цикла не содержит или содержит очень мало вычислений. Я думаю, ему не нравятся сложные индексаторы.   -  person James Dunne    schedule 23.12.2012
comment
На самом деле я получаю 1101 за тривиальный случай копирования...   -  person Mysticial    schedule 23.12.2012


Ответы (2)


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

Учти это:

int main(){
    byte  *source = new byte[1000];
    DWORD *dest   = new DWORD[1000];

    for (int i = 0; i < 200; ++i) {
        dest[(i*2*4096)+0] = (source[(i*8)+0]);
    }
    for (int i = 0; i < 200; ++i) {
        dest[i*2*4096] = source[i*8];
    }
    for (int i = 0; i < 200; ++i) {
        dest[i*8192] = source[i*8];
    }
    for (int i = 0; i < 200; ++i) {
        dest[i] = source[i];
    }
}

Вывод компилятора:

main.cpp(10) : info C5002: loop not vectorized due to reason '1200'
main.cpp(13) : info C5002: loop not vectorized due to reason '1200'
main.cpp(16) : info C5002: loop not vectorized due to reason '1203'
main.cpp(19) : info C5002: loop not vectorized due to reason '1101'

Давайте разберем это:

  1. Первые две петли одинаковые. Таким образом, они указывают исходную причину 1200, которая является зависимостью, переносимой циклом.

  2. #P6#
    #P7#
    #P8#
  3. #P9#
    #P10#
    #P11# #P12#

Так что единственное, что необычно, это то, почему начальный цикл сообщает о зависимости, переносимой циклом.
Ну, это сложный вызов... Я бы даже сказал, что компилятор просто не выдает правильная причина. (Когда это действительно должен быть несмежный доступ.)

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

Поэтому, если это имеет значение, я предлагаю вручную векторизовать этот цикл. Вам понадобится встроенная функция _mm_cvtepu8_epi32().


Ваш исходный цикл:

for (int i = 0; i < count; ++i) {
    dest[(i*2*pitch)+0] = (source[(i*8)+0]);
    dest[(i*2*pitch)+1] = (source[(i*8)+1]);
    dest[(i*2*pitch)+2] = (source[(i*8)+2]);
    dest[(i*2*pitch)+3] = (source[(i*8)+3]);

    dest[((i*2+1)*pitch)+0] = (source[(i*8)+4]);
    dest[((i*2+1)*pitch)+1] = (source[(i*8)+5]);
    dest[((i*2+1)*pitch)+2] = (source[(i*8)+6]);
    dest[((i*2+1)*pitch)+3] = (source[(i*8)+7]);
}

векторизует следующим образом:

for (int i = 0; i < count; ++i) {
    __m128i s0 = _mm_loadl_epi64((__m128i*)(source + i*8));
    __m128i s1 = _mm_unpackhi_epi64(s0,s0);

    *(__m128i*)(dest + (i*2 + 0)*pitch) = _mm_cvtepu8_epi32(s0);
    *(__m128i*)(dest + (i*2 + 1)*pitch) = _mm_cvtepu8_epi32(s1);
}

Отказ от ответственности: это не проверено и игнорирует выравнивание.

person Mysticial    schedule 23.12.2012
comment
Принятый ответ с учетом минимального объема контекста, который я предоставил. Реальная проблема заключалась в том, чтобы взять индексы цветов с палитрой 8bpp и найти их цвета ARGB DWORD в палитре, а затем смешать их в альфа-канале с некоторым глобальным цветом ARGB. Я сделал это с помощью встроенных функций SSE2 вместо того, чтобы полагаться на компилятор для автоматической векторизации. - person James Dunne; 27.12.2012
comment
Я пытался свести цикл к простейшему случаю, который заставил бы авто-векторизатор взять верх, и при этом я разрушил контекст исходной проблемы. Я полагаю, что мои ожидания были слишком велики от авто-векторизатора. - person James Dunne; 27.12.2012

Из документации MSDN случай, в котором будет сообщено об ошибке 1203

void code_1203(int *A)
{
    // Code 1203 is emitted when non-vectorizable memory references
    // are present in the loop body. Vectorization of some non-contiguous 
    // memory access is supported - for example, the gather/scatter pattern.

    for (int i=0; i<1000; ++i)
    {
        A[i] += A[0] + 1;       // constant memory access not vectorized
        A[i] += A[i*2+2] + 2;  // non-contiguous memory access not vectorized
    }
}

Это действительно могут быть вычисления в индексах, которые мешают авто-векторизатору. Забавно, но показанный код ошибки не 1203.

Сообщения MSDN Parallelizer и Vectorizer

person Castilho    schedule 23.12.2012