_mm256_load_ps 在调试模式下导致 google/benchmark 出现分段错误

Posted

技术标签:

【中文标题】_mm256_load_ps 在调试模式下导致 google/benchmark 出现分段错误【英文标题】:_mm256_load_ps cause segmentation fault with google/benchmark in debug mode 【发布时间】:2020-06-11 12:05:50 【问题描述】: 以下代码可以在发布模式和调试模式下运行。
#include <immintrin.h>

constexpr int n_batch = 10240;
constexpr int n = n_batch * 8;
#pragma pack(32)
float a[n];
float b[n];
float c[n];
#pragma pack()

int main() 
    for(int i = 0; i < n; ++i)
        c[i] = a[i] * b[i];

    for(int i = 0; i < n; i += 4) 
        __m128 av = _mm_load_ps(a + i);
        __m128 bv = _mm_load_ps(b + i);
        __m128 cv = _mm_mul_ps(av, bv);
        _mm_store_ps(c + i, cv);
    

    for(int i = 0; i < n; i += 8) 
        __m256 av = _mm256_load_ps(a + i);
        __m256 bv = _mm256_load_ps(b + i);
        __m256 cv = _mm256_mul_ps(av, bv);
        _mm256_store_ps(c + i, cv);
    


以下代码只能在发布模式下运行,在调试模式下会出现分段错误。
#include <immintrin.h>

#include "benchmark/benchmark.h"

constexpr int n_batch = 10240;
constexpr int n = n_batch * 8;
#pragma pack(32)
float a[n];
float b[n];
float c[n];
#pragma pack()

static void BM_Scalar(benchmark::State &state) 
    for(auto _: state)
        for(int i = 0; i < n; ++i)
            c[i] = a[i] * b[i];

BENCHMARK(BM_Scalar);

static void BM_Packet_4(benchmark::State &state) 
    for(auto _: state) 
        for(int i = 0; i < n; i += 4) 
            __m128 av = _mm_load_ps(a + i);
            __m128 bv = _mm_load_ps(b + i);
            __m128 cv = _mm_mul_ps(av, bv);
            _mm_store_ps(c + i, cv);
        
    

BENCHMARK(BM_Packet_4);

static void BM_Packet_8(benchmark::State &state) 
    for(auto _: state) 
        for(int i = 0; i < n; i += 8) 
            __m256 av = _mm256_load_ps(a + i); // Signal: SIGSEGV (signal SIGSEGV: invalid address (fault address: 0x0))
            __m256 bv = _mm256_load_ps(b + i);
            __m256 cv = _mm256_mul_ps(av, bv);
            _mm256_store_ps(c + i, cv);
        
    

BENCHMARK(BM_Packet_8);

BENCHMARK_MAIN();

【问题讨论】:

您忘记了 alignas(32) float a[n]; 来匹配您选择的对齐要求的负载内在函数。 docs.microsoft.com/en-us/cpp/preprocessor/pack?view=vs-2019 说#pragma pack(8) 只影响 struct/union/class 对象,这将是 8 字节对齐,而不是 8 个浮点数的块。 @PeterCordes 抱歉#pragma pack(8) 的拼写错误。 #pragma pack(32) 的错误仍然存​​在。 对,因为数组不是结构、联合或类。那个 pragma 对他们没有影响。这是你问题的真正答案吗?当前的副本涵盖了未优化与优化构建之间存在差异的原因。 (我假设使用 GGC 或 clang?MSVC 避免了对齐的加载/存储指令。) 是的,alignas(32) 确实有效。但是为什么#pragma pack(32) 不起作用? 【参考方案1】:

您的数组未按 32 对齐。您可以使用调试器进行检查。

#pragma pack(32) 仅对齐结构/联合/类成员,as documented by MS。 C++ 数组是一种不同类型的对象,完全不受 MSVC 编译指示的影响。 (不过,我认为您实际上使用的是 GCC 或 clang 的版本,因为 MSVC 通常使用 vmovups 而不是 vmovaps

对于静态或自动存储(非动态分配)中的数组,在 C++11 及更高版本中对齐数组的最简单方法是 alignas(32)。这是完全可移植的,不像 GNU C __attribute__((aligned(32))) 或任何 MSVC 的等价物。

alignas(32) float a[n];
alignas(32) float b[n];
alignas(32) float c[n];

AVX: data alignment: store crash, storeu, load, loadu doesn't 解释了为什么根据优化级别存在差异:优化代码会将一个负载折叠到 vmulps 的内存源操作数中,这(与 SSE 不同)不需要对齐。 (大概第一个数组恰好对齐了。)

未优化的代码将单独执行 _mm256_load_psvmovaps 对齐要求的负载。

_mm256_loadu_ps 将始终避免使用需要对齐的加载,因此如果您不能保证数据对齐,请使用它。)

【讨论】:

以上是关于_mm256_load_ps 在调试模式下导致 google/benchmark 出现分段错误的主要内容,如果未能解决你的问题,请参考以下文章

_mm256_loadu2_m128i 内在函数在 g++ 下不可用?

仅在发布模式下将 __m256i 存储在 std::vector 中会产生访问冲突[重复]

AVX2 的汇编错误

在AVX2中重现_mm256_sllv_epi16和_mm256_sllv_epi8

_mm256_testc_pd、_mm256_testz_pd、_mm256_testnzc_pd 是干啥用的?

AVX 内在 _mm256_cmp_ps 是不是应该在为真时返回 NaN?