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 исчезнет. Скрестим пальцы, мы никогда не сталкивались с этой ошибкой во время производства (съеживаемся).


person Eric Roller    schedule 08.02.2019    source источник
comment
Я предполагаю, что это связано с gcc.gnu.org/bugzilla/show_bug.cgi? идентификатор=88998   -  person cpplearner    schedule 09.02.2019


Ответы (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 и наоборот.

person Florian Weimer    schedule 09.02.2019
comment
Спасибо. Я проверил обходной путь. Некоторые дополнительные замечания: - _m_maskmovq указан как инструкция SSE, а не ММХ. Это сбивало с толку. - bad_alloc происходит только с initalizer_list. Есть идеи, почему? - если у меня многопоточный код, то я не вижу, как мне поможет _mm_empty? - person Eric Roller; 11.02.2019
comment
Вычисление внутри конструктора использует long double, см. GCC PR, на который я ссылался. На веб-странице, на которую вы ссылаетесь, написано Флаги CPUID: SSE, что говорит вам, как проверить поддержку инструкции. Это не означает, что функция находится в наборе инструкций SSE (и это не так, потому что она работает с регистрами MMX). - person Florian Weimer; 11.02.2019
comment
Упомянутый PR GCC использует конструктор по умолчанию unordered_map. Оптимизируется ли тогда код в этом PR для использования конструктора initializer_list? Когда я изменяю свой код, чтобы использовать конструктор по умолчанию с последующими вызовами для вставки, bad_alloc исчезает. - person Eric Roller; 11.02.2019
comment
_M_bkt_for_elements использует long double и, следовательно, FPU. По-видимому, он используется только со списками инициализаторов. - person Florian Weimer; 12.02.2019