将 Eigen 与“-march=native”一起使用时,导致此内存访问冲突错误 (0xC0000005) 的原因是什么?

What is causing this memory access violation error (0xC0000005) when using Eigen with "-march=native"?

我正在代码块中重写一些 c++ 代码(最初作为 MEX 函数在 Matlab 中编写),以便我可以使用为 c++ 设计的调试和分析工具。我正在重写的代码使用 Eigen 和 SIMD 内部指令,因此我需要使用 -march=native 标志进行编译。当 运行 我的主项目时,我遇到了内存访问冲突错误。这是导致问题的代码的简化版本:

#include <iostream>
#include <fstream>
#include <string>
#include <sys/stat.h>
#include <immintrin.h>
#include <Eigen/Dense>
#include "Parameters.h"

using namespace std;

int main()
{
    
    Parameters p;
    p.na = 16;
    p.TXangle = Eigen::VectorXd::LinSpaced(p.na,0,p.na-1);

    cout << p.TXangle << endl;

    cout << "Hello world!" << endl;
    return 0;
}

其中参数是使用以下两个文件定义的自定义 class:

Parameters.h

#ifndef PARAMETERS_H_INCLUDED
#define PARAMETERS_H_INCLUDED

class Parameters
{
    public:
        int na;
        Eigen::VectorXd TXangle;

        Parameters();

};

#endif // PARAMETERS_H_INCLUDED

Parameters.cpp

#include <string>
#include <Eigen/Dense>
#include "Parameters.h"

Parameters::Parameters()
{
    //ctor
}

断开的行是在初始化 p.TXangle 时。此时,程序抛出 (0xC0000005) 错误。如果我不使用“-march=native”进行编译,那么错误就不会发生并且程序运行正常。使用“-march=native”构建时,我还会收到几个对齐警告。我的计算机最多支持 AVX2 指令,并且我正在使用 MinGW GCC 进行编译(不确定如何检查代码块上的 gcc 版本)。

gcc 版本为 8.1.0

更新: 这是@Sedenion 在评论中询问的价值吗?

这是调试器停止的确切行:

**更新:** 根据评论中的讨论,反汇编程序在失败时显示代码在这条汇编指令处:

我很难理解这个,阅读汇编对我来说还是有点新鲜。这是同一点的寄存器:

我使用 Eigen 3.4.0 和 mingw(gcc 8.1.0 with -mavx -m64 -std=c++17 -g)在 Windows 上使用 AVX(-mavx,也由 -march=native 用于 OP)。正如评论中的人们已经怀疑的那样,mingw-gcc 无法将堆栈变量正确对齐到 32 字节,这肯定是 AVX 所要求的问题(比较 bug issue for gcc, also see e.g. )。

崩溃 VectorXd 的使用无关 (由于它使用动态内存分配,因此不应受到影响)。相反, Eigen::VectorXd::LinSpaced() 调用是问题所在。在 Eigen 中,这最终会调用以下涉及 PacketMath.h 中的 AVX 指令的函数:

template<> EIGEN_STRONG_INLINE Packet4d plset<Packet4d>(const double& a) {
 return _mm256_add_pd(_mm256_set1_pd(a), _mm256_set_pd(3.0,2.0,1.0,0.0)); 
}

在这个调用中,涉及临时堆栈变量,它们不是 mingw 对齐的 32 字节。有一次,一个对齐的 mov vmovapd 被尝试到这样一个 non-aligned 地址:

mov    rax,QWORD PTR [rbp+0x10]
vmovapd YMMWORD PTR [rax],ymm0

例如,在一个 运行 中,我得到的 rax=0x67f890 只有 16 字节而不是 32 字节对齐。捕获行为的最小可重现示例如下(https://godbolt.org/z/qbE6z1nb8,请注意 godbolt 不支持 mingw,因此问题不会出现在那里):

#include <iostream>
#include <immintrin.h>

__m256d Set(const double&) {
   __m256d temp = _mm256_setzero_pd(); // Crashes on mingw
   return temp;
}

int main() {
    Set(2);
    std::cerr << "End" << std::endl;
}

Set() 的未使用参数只是为了在堆栈上获取偏移量以触发问题。 它在使用 mingw-gcc(使用 -mavx -m64)时崩溃,但是在 Windows 上使用 clang 或 MSVC 编译时 运行 没问题。它也 运行 在我试过的所有 Linux 编译器上都很好。

所以,简而言之,你的代码是正确的,崩溃发生在Eigen。 no "magic switch" for gcc 可以解决这个问题。因此,我猜你有 3 个选择:

  • 等待 mingw problem 修复。根据帖子,它仍然存在于 gcc 11.2.0 中。但考虑到历史悠久,我怀疑它会很快得到修复。
  • 不要使用 AVX 或更高版本(或 -march=native)编译,而是坚持使用 <=SSE4.2。当然,这 可能 影响性能。我建议进行分析以确定您是否属于这种情况。
  • 使用其他编译器,例如 clang 或 MSVC。