Кто-нибудь видит что-то очевидное в коде цикла ниже, что я не вижу, почему это не может быть автоматически векторизовано компилятором C ++ 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]);
}
Скажем так, есть нечто большее, чем просто пара вещей, препятствующих векторизации этого цикла …
Учти это:
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'
Давайте разберем это:
Первые две петли одинаковы. Таким образом, они дают оригинальную причину 1200
которая является циклической зависимостью.
3-й цикл такой же, как 2-й цикл. Тем не менее, компилятор дает другую причину 1203
:
Тело цикла включает несмежные обращения в массив
Хорошо … Почему другая причина? Я не знаю. Но на этот раз причина верна.
4-й цикл дает 1101
:
Цикл содержит не векторизуемую операцию преобразования (может быть неявной)
Так что VC ++ не настолько умен, чтобы выпускать SSE4.1 pmovzxbd
инструкция.
Это довольно нишевый случай, я бы не ожидал, что какой-либо современный компилятор сможет это сделать. И если это возможно, вам нужно будет указать SSE4.1.
Так что единственное, что необычно, это то, почему начальный цикл сообщает о переносимой в цикле зависимости.
Ну, это сложный вызов … Я бы сказал, что компилятор просто не выдает правильную причину. (Когда это действительно должен быть несмежный доступ.)
Возвращаясь к делу, я бы не ожидал, что 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);
}
Отказ от ответственности: это не проверено и игнорирует выравнивание.
Из документации 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.