1
  • 以下代码可以在发布模式和调试模式下运行。
#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();
4

1 回答 1

6

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

#pragma pack(32)仅对齐 struct/union/class 成员,如 MS 所述。C++ 数组是一种不同类型的对象,根本不受 MSVC 编译指示的影响。(不过,我认为您实际上使用的是 GCC 或 clang 的版本,因为 MSVC 通常使用vmovupsnot 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 没有解释为什么存在差异取决于优化级别:优化的代码会将一个加载折叠到vmulps不需要对齐的内存源操作数中(与 SSE 不同)。(大概第一个数组恰好是充分对齐的。)

未优化的代码将_mm256_load_ps单独执行vmovaps需要对齐的加载。

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

于 2020-06-11T13:09:32.210 回答