bad_alloc 与 unordered_map initializer_list 和 MMX 指令,可能是堆损坏?

bad_alloc with unordered_map initializer_list and MMX instruction, possible heap corruption?

我从下面用 gcc 编译的代码(尝试过 4.9.3、5.40 和 6.2)抛出 bad_alloc。 gdb 告诉我它发生在最后一行 initalizer_list 代表 unordered_map。如果我注释掉 mmx 指令 _m_maskmovq 就没有错误。同样,如果我注释掉 unordered_map 的初始化,这也没有错误。只有在调用 mmx 指令并使用 initializer_list 初始化 unordered_map 时,我才会得到 bad_alloc。如果我默认构造 unordered_map 并调用 map.emplace(1,1) 也没有错误。我已经 运行 在具有 48 个内核(英特尔至强)和 376 GB RAM 的 centos7 机器上以及 Ubuntu WSL 下的戴尔笔记本电脑(英特尔核心 i7)上获得了相同的结果。这里发生了什么? MMX 指令是否破坏了堆? Valgrind 似乎没有发现任何有用的东西。

编译器命令和输出:

$g++ -g -std=c++11 main.cpp
$./a.out
   terminate called after throwing an instance of 'std::bad_alloc'
   what():  std::bad_alloc
   Aborted

源代码(main.cpp):

#include <immintrin.h>
#include <unordered_map>

int main()
{
  __m64 a_64 = _mm_set_pi8(0,0,0,0,0,0,0,0);
  __m64 b_64 = _mm_set_pi8(0,0,0,0,0,0,0,0);
  char dest[8] = {0};
  _m_maskmovq(a_64, b_64, dest);

  std::unordered_map<int, int> map{{ 1, 1}};
}

更新: _mm_empty() 解决方法确实修复了此示例。当使用一个线程执行矢量指令而另一个线程使用 unordered_map 的多线程代码时,这似乎不是一个可行的解决方案。另一个有趣的点是,如果我打开 -O3 优化,bad_alloc 就会消失。祈祷我们在生产过程中从未遇到过这个错误(畏缩)。

没有堆损坏。发生这种情况是因为 std::unordered_map 在内部使用 long double,用于根据初始化程序中的元素数量计算存储桶计数(请参阅 libstdc++ 源代码中的 _Prime_rehash_policy::_M_bkt_for_elements)。

在从 MMX 代码切换到 FPU 代码之前需要调用 _mm_empty。这与将 FPU 寄存器重用于 MMX 寄存器文件的历史性决定有关(与现代 CPU 中的寄存器重命名相反)。

如果添加 _mm_empty 调用,异常就会消失:

…
  _m_maskmovq(a_64, b_64, dest);
  _mm_empty();
  std::unordered_map<int, int> map{{ 1, 1}};
…

参见GCC PR 88998, as identified by cpplearner

ongoing work to implement the MMX intrinsics with SSE on x86-64,这将使这个问题消失,因为SSE指令不影响FPU状态,反之亦然。