Поскольку vshufps
и vpermps
оба выполняются на порту 5 (Intel Skylake), я бы предпочел vblendps
+ vpermps
, а не vshufps
+ vpermps
, для лучшего сочетания инструкций. На Intel skylake vblendps
может выполняться на порте 0, 1 или 5. Следующее решение использует 2 перекрывающиеся векторные загрузки:
#include <stdio.h>
#include <immintrin.h>
__m256 stide_2_load_odd(float * a){
__m256 x_lo = _mm256_loadu_ps(&a[1]);
__m256 x_hi = _mm256_loadu_ps(&a[8]);
__m256 x_b = _mm256_blend_ps(x_lo, x_hi, 0b10101010);
__m256 y = _mm256_permutevar8x32_ps(x_b, _mm256_set_epi32(7,5,3,1,6,4,2,0));
return y;
}
__m256 stide_2_load_even(float * a){
__m256 x_lo = _mm256_loadu_ps(&a[0]);
__m256 x_hi = _mm256_loadu_ps(&a[7]);
__m256 x_b = _mm256_blend_ps(x_lo, x_hi, 0b10101010);
__m256 y = _mm256_permutevar8x32_ps(x_b, _mm256_set_epi32(7,5,3,1,6,4,2,0));
return y;
}
int main()
{
float a[] = {0.1, 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1, 8.1, 9.1, 10.1, 11.1, 12.1, 13.1, 14.1, 15.1};
float b[8];
__m256 y = stide_2_load_odd(a);
_mm256_storeu_ps(b, y);
printf("odd indices 1, 3, 5, ...\n");
for(int i=0; i<8; i++){
printf("y[%i] = %f \n", i, b[i]);
}
y = stide_2_load_even(a);
_mm256_storeu_ps(b, y);
printf("\neven indices 0, 2, 4, ...\n");
for(int i=0; i<8; i++){
printf("y[%i] = %f \n", i, b[i]);
}
return 0;
}
Результат:
$gcc -Wall -O3 -march=skylake -o main *.c
$main
odd indices 1, 3, 5, ...
y[0] = 1.100000
y[1] = 3.100000
y[2] = 5.100000
y[3] = 7.100000
y[4] = 9.100000
y[5] = 11.100000
y[6] = 13.100000
y[7] = 15.100000
even indices 0, 2, 4, ...
y[0] = 0.100000
y[1] = 2.100000
y[2] = 4.100000
y[3] = 6.100000
y[4] = 8.100000
y[5] = 10.100000
y[6] = 12.100000
y[7] = 14.100000
Здесь невыровненные нагрузки используются. На современных процессорах это не приводит к снижению производительности, если операция чтения из памяти не пересекает границу строки кэша. Поэтому предпочтительно вызывать эти две функции с адресом, выровненным по 64 байта a
. См. Также комментарий Питера Кордеса .