最快的步幅 2 聚集

Posted

技术标签:

【中文标题】最快的步幅 2 聚集【英文标题】:fastest stride 2 gather 【发布时间】:2020-07-11 11:58:48 【问题描述】:

我知道在使用 AVX2 进行快速步幅 3 收集时存在问题。我想知道最快的步幅 2 收集序列是什么,假设我想将长度为 16 向量的所有奇数元素加载到 ymm0

特别是,我想知道的相对收益和成本

    使用步幅为 2 的 AVX2 聚集和 发出两个向量加载,然后使用一系列混合和随机播放指令。

如果 2) 总是比 1) 好,那么最好的指令顺序是什么?

【问题讨论】:

你能澄清一下元素的大小吗? 最佳解决方案将取决于周围代码的端口使用情况(也取决于您是否还需要偶数元素),但聚集不太可能是最有效的方式。 对于 32 位或 64 位的元素大小,256 位加载 + vshufps +(vpermdvpermq)可能是最好的。 是否需要保留元素的相对顺序?或者,如果元素以其他一些但可预测的顺序出现,那也没关系? 大小为 32 位浮点数。我需要保留元素的相对顺序 【参考方案1】:

由于vshufpsvpermps 都在端口5(英特尔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 的 comment。

【讨论】:

请注意vblendps+vpermps 几乎肯定会比vgatherdps 快,请参阅chtz' 评论。 vshufps 允许对齐两个负载。但是,如果您知道相对于 64 字节对齐的位置,则可以通过在缓存行的开头将 _mm256_loadu_ps(&amp;a[1]);a 放在一起而不是重叠到下一个缓存行来避免缓存行拆分。然后它仍然是 Intel 的完整性能。 您想要一个 64 字节 对齐的 a。如果它是 32 但 不是 64,您的第一次加载将缓存拆分。

以上是关于最快的步幅 2 聚集的主要内容,如果未能解决你的问题,请参考以下文章

对大文件执行 FFT 的最快方法是啥?

索引碎片检测和整理

检测和整理索引碎片

索引碎片的检测和整理

POJ 1700

PHP如何加速到最快