bad_alloc с unordered_map initializer_list и инструкцией MMX, возможно ли повреждение кучи?
Я получаю bad_alloc
выкинут из кода ниже скомпилированного с gcc (пробовал 4.9.3, 5.40 и 6.2). GDB говорит мне, что это происходит в последней строке с initalizer_list для unordered_map. Если я закомментирую инструкцию mmx _m_maskmovq
нет ошибки Точно так же, если я закомментирую инициализацию unordered_map, это не ошибка. Только при вызове инструкции mmx и инициализации unordered_map с помощью initializer_list я получаю bad_alloc
, Если я по умолчанию создаю unordered_map и вызываю map.emplace(1,1)
Там также нет ошибки. Я запустил это на машине centos7 с 48 ядрами (intel xeon) и 376 ГБ оперативной памяти, а также на ноутбуке Dell (intel core i7) под Ubuntu WSL с тем же результатом. Что здесь происходит? Инструкция MMX портит кучу? Вальгринд, похоже, не нашел ничего полезного.
Команда и вывод компилятора:
$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 уходит. Скрестив пальцы, мы никогда не сталкивались с этой ошибкой во время производства.
1 ответ
Там нет кучи коррупции. Это происходит потому, что std::unordered_map
использования long double
внутренне, для вычисления количества сегментов из числа элементов в инициализаторе (см. _Prime_rehash_policy::_M_bkt_for_elements
в источниках libstdC++).
Надо позвонить _mm_empty
перед переключением с кода MMX на код FPU. Это связано с историческим решением повторно использовать регистры FPU для файла регистров MMX (в отличие от переименования регистров в современных процессорах).
Исключение исчезает, если _mm_empty
звонок добавлен:
…
_m_maskmovq(a_64, b_64, dest);
_mm_empty();
std::unordered_map<int, int> map{{ 1, 1}};
…
См. GCC PR 88998, как указано cpplearner.
Продолжается работа по внедрению встроенных функций MMX в SSE на платформе x86-64, что устранит эту проблему, поскольку инструкции SSE не влияют на состояние FPU и наоборот.