2016-07-05 15 views
6

У меня есть выровненный массив целых чисел в памяти, содержащий индексы I0, I1, I2, I3. Моя цель - собрать их в регистр __m256i, содержащий I0, I0 + 1, I1, I1 + 1, I2, I2 + 1, I3, I3 + 1. Жесткая часть получает их в 256-битный регистр как I0, I0 , I1, I1, I2, I2, I3, I3, после чего я могу просто добавить регистр, содержащий 0, 1, 0, 1, 0, 1, 0, 1.AVX2, как эффективно загружать четыре целых числа в четные индексы с 256-битным регистром и копировать в нечетные индексы?

Я нашел встроенный _mm256_castsi128_si256, который позволяет мне загружать 4 целых числа в младшие 128 бит 256-битного регистра, но я изо всех сил стараюсь найти лучшие возможности для использования оттуда.

Любая помощь будет оценена по достоинству. Я имею доступ ко всем версиям SSE, AVX и AVX2 и хотел бы сделать это, используя только встроенные функции.

Edit:

Я думаю, что это работает, но я не насколько эффективно это ... в процессе тестирования.

// _mm128_load_si128: Loads 4 integer values into a temporary 128bit register. 
// _mm256_broadcastsi128_si256: Copies 4 integer values in the 128 bit register to the low and high 128 bits of the 256 bit register. 
__m256i tmpStuff = _mm256_broadcastsi128_si256 ((_mm_load_si128((__m128i*) indicesArray))); 

// _mm256_unpacklo_epi32: Interleaves the integer values of source0 and source1. 
__m256i indices = _mm256_unpacklo_epi32(tmpStuff, tmpStuff); 

__m256i regToAdd = _mm256_set_epi32 (0, 1, 0, 1, 0, 1, 0, 1); 
indices = _mm256_add_epi32(indices, regToAdd); 

Edit2: Приведенный выше код не работает, потому что _mm256_unpacklo_epi32 не ведет себя так, как я думал. Приведенный выше код приведет к I0, I0 + 1, I1, I1 + 1, I0, I0 + 1, I1, I1 + 1.

Edit3: Следующий код работает, хотя с другой стороны я не уверен, что это самый эффективный:

__m256i tmpStuff = _mm256_castsi128_si256(_mm_loadu_si128((__m128i*) indicesArray)); 
__m256i mask = _mm256_set_epi32 (3, 3, 2, 2, 1, 1, 0, 0); 
__m256i indices= _mm256_permutevar8x32_epi32(tmpStuff, mask); 
__m256i regToAdd = _mm256_set_epi32 (1, 0, 1, 0, 1, 0, 1, 0); // Set in reverse order. 
indices= _mm256_add_epi32(indices, regToAdd); 

ответ

6

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

1 нагрузка (vmovdqa), 1 перетасовка (vpermd, ака _mm256_permutevar8x32_epi32) и 1 дополнительный (vpaddd) довольно легкий. Если вы не выполняете невыровненную нагрузку 256b с разделом между элементами 1 и 2, вам понадобится какой-то перетасовочный переход, чтобы получить верхние 2 элемента в верхние 128b.

Поскольку вы можете использовать AVX2, ваше решение отлично, если загрузка маски тасования для vpermd не является проблемой. (пропуски давления в регистре/кеш).


Альтернативы, что позволяет избежать вектора постоянной перетасовки-маски, но хуже, иначе:

vpmovzxdq является еще одним вариантом для получения два верхних элементов в верхнюю полосу 128bit.

vpmovzxdq ymm0, [src] 
vpshufd ymm1, ymm0, _MM_SHUFFLE(2,2, 0,0) ; duplicate elements 
vpaddd  ... 

Или, возможно, более высокую пропускную способность, если перетасовать порт является узким местом для всего цикла. (Еще хуже, чем версия vpermd в этом вопросе, хотя.)

vpmovzxdq ymm0, [src] 
vpsrlq  ymm1, ymm0,32  ; left shift by 32 
vpaddd  ...     ; ymm1 +=1 in odd elements only 
vpor  ...     ; OR the incremented odd elements with the original even elements 

сдвига и OR заменить перетасовать в-лейн.