_mm256_load_ps 在调试模式下使用 google/benchmark 导致分段错误
_mm256_load_ps cause segmentation fault with google/benchmark in debug mode
- 以下代码可以运行在发布和调试模式下。
#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);
}
}
- 下面的代码只能运行在release模式下,在debug模式下会报segmentation fault
#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();
你的数组没有按 32 对齐。你可以用调试器检查一下。
#pragma pack(32)
仅对齐 struct/union/class 个成员,as documented by MS。 C++ 数组是一种不同类型的对象,完全不受 MSVC pragma 的影响。 (不过,我认为您实际上使用的是 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];
解释了为什么根据优化级别存在差异:优化代码会将一个负载折叠到 vmulps
的内存源操作数中,这(与 SSE 不同)不需要对齐。 (大概第一个数组恰好对齐了。)
未优化的代码将使用 vmovaps
需要对齐的负载单独执行 _mm256_load_ps
。
(_mm256_loadu_ps
将始终避免使用需要对齐的加载,因此如果您不能保证数据对齐,请使用它。)
- 以下代码可以运行在发布和调试模式下。
#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);
}
}
- 下面的代码只能运行在release模式下,在debug模式下会报segmentation fault
#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();
你的数组没有按 32 对齐。你可以用调试器检查一下。
#pragma pack(32)
仅对齐 struct/union/class 个成员,as documented by MS。 C++ 数组是一种不同类型的对象,完全不受 MSVC pragma 的影响。 (不过,我认为您实际上使用的是 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];
vmulps
的内存源操作数中,这(与 SSE 不同)不需要对齐。 (大概第一个数组恰好对齐了。)
未优化的代码将使用 vmovaps
需要对齐的负载单独执行 _mm256_load_ps
。
(_mm256_loadu_ps
将始终避免使用需要对齐的加载,因此如果您不能保证数据对齐,请使用它。)