最快步幅 2 聚集

fastest stride 2 gather

我知道有一个关于使用 AVX2 快速 stride-3 gather 的问题。我想知道最快的 stride 2 gather 序列是什么,比如我想将长度为 16 的向量的所有奇数元素加载到 ymm0.

特别是,我想知道

的相对收益和成本
  1. 使用步幅为 2 的 AVX2 收集和
  2. 发出两个矢量加载,然后使用一系列混合和洗牌指令。

如果 2) 总是优于 1),使用指令的最佳顺序是什么?

因为 vshufpsvpermps 都在端口 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 

这里使用了未对齐的负载。在现代 cpu 上,只要从内存中读取操作不跨越任何缓存行边界,这些不会导致任何性能损失。因此,最好使用 64 字节对齐的地址 a 来调用这两个函数。另见 Peter Cordes 的 .