SIMD 聚集引起的分段错误?
Segmentation fault caused by SIMD gather?
我的项目使用 SIMD 收集来加速 table 查找。以下是简化版本,但足以说明我遇到的问题。
#include <x86intrin.h>
#include <stdio.h>
alignas(32) static int a[256][8] = { 0 };
int main(){
// initialize 32 bytes (as a __m256i)
int *s = (int*)_mm_malloc(32, 4);
for(int i=0; i<8; i++)
s[i] = i;
__m256i *t = (__m256i*)s;
// do table lookup task using SIMD gather
for(int i=0; i<100000; i++){
int *addr = a[i % 256];
t[0] = _mm256_i32gather_epi32(addr, t[0], 4);
}
// print out the result
for(int i=0; i<8; i++)
printf("%d ", s[i]);
printf("\n");
}
编译和执行
user@server:~/test$ g++ -O3 -mavx2 gather.cpp
user@server:~/test$ ./a.out
Segmentation fault (core dumped)
实际上,还有一个使用 SIMD shuffle 和 __m128i 的替代版本,它可以正常工作。有人知道吗?
_mm_malloc (size_t size, size_t align)
- 您仅对齐 4,然后对 __m256i*
执行需要对齐的取消引用。据推测,当 _mm_malloc(32, 4)
发生在 return 未按 32 对齐的内存时会出现段错误。
像普通人一样使用_mm256_set_epi32(7,6,5,4,3,2,1,0);
,或者alignas(32)
一个你可以在循环中初始化的本地数组。 (And/or 您可以使用 _mm256_loadu_si256
进行未对齐加载)。
您可以使用_mm_malloc(32,32)
修复您的代码,但不要这样做。动态分配(然后泄漏)您只想供本地使用的单个 32 字节对象是非常愚蠢的。
当所有数据都来自一个或两个 32 字节块时,更喜欢随机播放而不是收集
一个 8 元素的集合在缓存访问方面的成本大约为 8 个标量或矢量加载,加上其他执行单元的一些工作。 (https://uops.info/ and https://agner.org/optimize/)。不幸的是,当多个元素来自同一缓存行时,Gather 并没有变得更有效率。
在您的情况下,您甚至不需要随机播放,只需从 a[][]
.
的一部分加载 32 字节
int *addr = a[i % 256];
得到一个指向 32 字节对齐的 int [8]
的指针,从中可以 _mm256_load_si256((const __m256i*)addr)
。这为您提供了您想要的 0..7 本机顺序的元素。
如果您确实想要 0..7 以外的订单,请使用 AVX2 vpermd
(_mm256_permutevar8x32_epi32
) 以及您用作收集索引的相同洗牌控制向量常数。
我的项目使用 SIMD 收集来加速 table 查找。以下是简化版本,但足以说明我遇到的问题。
#include <x86intrin.h>
#include <stdio.h>
alignas(32) static int a[256][8] = { 0 };
int main(){
// initialize 32 bytes (as a __m256i)
int *s = (int*)_mm_malloc(32, 4);
for(int i=0; i<8; i++)
s[i] = i;
__m256i *t = (__m256i*)s;
// do table lookup task using SIMD gather
for(int i=0; i<100000; i++){
int *addr = a[i % 256];
t[0] = _mm256_i32gather_epi32(addr, t[0], 4);
}
// print out the result
for(int i=0; i<8; i++)
printf("%d ", s[i]);
printf("\n");
}
编译和执行
user@server:~/test$ g++ -O3 -mavx2 gather.cpp
user@server:~/test$ ./a.out
Segmentation fault (core dumped)
实际上,还有一个使用 SIMD shuffle 和 __m128i 的替代版本,它可以正常工作。有人知道吗?
_mm_malloc (size_t size, size_t align)
- 您仅对齐 4,然后对 __m256i*
执行需要对齐的取消引用。据推测,当 _mm_malloc(32, 4)
发生在 return 未按 32 对齐的内存时会出现段错误。
像普通人一样使用_mm256_set_epi32(7,6,5,4,3,2,1,0);
,或者alignas(32)
一个你可以在循环中初始化的本地数组。 (And/or 您可以使用 _mm256_loadu_si256
进行未对齐加载)。
您可以使用_mm_malloc(32,32)
修复您的代码,但不要这样做。动态分配(然后泄漏)您只想供本地使用的单个 32 字节对象是非常愚蠢的。
当所有数据都来自一个或两个 32 字节块时,更喜欢随机播放而不是收集
一个 8 元素的集合在缓存访问方面的成本大约为 8 个标量或矢量加载,加上其他执行单元的一些工作。 (https://uops.info/ and https://agner.org/optimize/)。不幸的是,当多个元素来自同一缓存行时,Gather 并没有变得更有效率。
在您的情况下,您甚至不需要随机播放,只需从 a[][]
.
int *addr = a[i % 256];
得到一个指向 32 字节对齐的 int [8]
的指针,从中可以 _mm256_load_si256((const __m256i*)addr)
。这为您提供了您想要的 0..7 本机顺序的元素。
如果您确实想要 0..7 以外的订单,请使用 AVX2 vpermd
(_mm256_permutevar8x32_epi32
) 以及您用作收集索引的相同洗牌控制向量常数。