首页 > 解决方案 > SIMD 聚集导致的分段错误?

问题描述

我的项目使用 SIMD 收集来加速表查找。以下是简化版本,但足以说明我遇到的问题。

#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)

实际上,还有一个使用带有 __m128i 的 SIMD shuffle 的替代版本,它可以正常工作。有人有想法吗?

标签: c++simdavxavx2

解决方案


_mm_malloc (size_t size, size_t align)- 你只对齐 4,然后对 a 进行对齐所需的取消引用__m256i*。据推测,当_mm_malloc(32, 4)碰巧返回未按 32 对齐的内存时,会出现段错误。

只需_mm256_set_epi32(7,6,5,4,3,2,1,0);像普通人一样使用,或者alignas(32)可以在循环中初始化的本地数组。(和/或你可以_mm256_loadu_si256用来做一个未对齐的负载)。

可以使用 修复您的代码_mm_malloc(32,32),但不要。动态分配(然后泄漏)一个您只想在本地使用的单个 32 字节对象是非常愚蠢的。


当所有数据都来自一个或两个 32 字节的块时,更喜欢 shuffle 而不是收集

就缓存访问而言,一个 8 元素的收集成本大约为 8 个标量或向量负载,加上其他执行单元的一些工作。(https://uops.info/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) 和您用作收集索引的相同随机控制向量常数。


推荐阅读