最快步幅 2 聚集
fastest stride 2 gather
我知道有一个关于使用 AVX2 快速 stride-3 gather 的问题。我想知道最快的 stride 2 gather 序列是什么,比如我想将长度为 16 的向量的所有奇数元素加载到 ymm0
.
特别是,我想知道
的相对收益和成本
- 使用步幅为 2 的 AVX2 收集和
- 发出两个矢量加载,然后使用一系列混合和洗牌指令。
如果 2) 总是优于 1),使用指令的最佳顺序是什么?
因为 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
这里使用了未对齐的负载。在现代 cpu 上,只要从内存中读取操作不跨越任何缓存行边界,这些不会导致任何性能损失。因此,最好使用 64 字节对齐的地址 a
来调用这两个函数。另见 Peter Cordes 的 .
我知道有一个关于使用 AVX2 快速 stride-3 gather 的问题。我想知道最快的 stride 2 gather 序列是什么,比如我想将长度为 16 的向量的所有奇数元素加载到 ymm0
.
特别是,我想知道
的相对收益和成本- 使用步幅为 2 的 AVX2 收集和
- 发出两个矢量加载,然后使用一系列混合和洗牌指令。
如果 2) 总是优于 1),使用指令的最佳顺序是什么?
因为 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
这里使用了未对齐的负载。在现代 cpu 上,只要从内存中读取操作不跨越任何缓存行边界,这些不会导致任何性能损失。因此,最好使用 64 字节对齐的地址 a
来调用这两个函数。另见 Peter Cordes 的