Огромная утечка памяти при объединении OpenMP, Intel MKL и компилятора MSVC

Я работаю над довольно большим проектом C++ с большим упором на производительность. Поэтому он использует библиотеку Intel MKL и OpenMP. Недавно я заметил значительную утечку памяти, которую я мог сузить до следующего минимального примера:

#include <atomic>
#include <iostream>
#include <thread>

class Foo {
public:
  Foo() : calculate(false) {}

  // Start the thread
  void start() {
    if (calculate) return;
    calculate = true;
    thread = std::thread(&Foo::loop, this);
  }

  // Stop the thread
  void stop() {
    if (!calculate) return;

    calculate = false;
    if (thread.joinable())
      thread.join();
  }

private:
  // function containing the loop that is continually executed
  void loop() {
    while (calculate) {
      #pragma omp parallel
      {
      }
    }
  }

  std::atomic<bool> calculate;
  std::thread thread;
};


int main() {
  Foo foo;
  foo.start();
  foo.stop();
  foo.start();

  // Let the program run until the user inputs something
  int a;
  std::cin >> a;

  foo.stop();
  return 0;
}

При компиляции с помощью Visual Studio 2013 и выполнении этот код теряет до 200 МБ памяти в секунду (!).

Немного изменив приведенный выше код, утечка полностью исчезнет. Например:

  • Если программа не связана с библиотекой MKL (которая здесь явно не нужна), утечки нет.
  • Если я скажу OpenMP использовать только один поток (т. е. я установлю переменную среды OMP_NUM_THREADS в 1), утечки не будет.
  • Если я закомментирую строку #pragma omp parallel, утечки не будет.
  • Если я не остановлю поток и не начну его снова с foo.stop() и foo.start(), утечки не будет.

Я делаю что-то неправильно здесь или я что-то упускаю?


person oLen    schedule 15.01.2016    source источник
comment
Я не уверен в этом, но я думаю, что объединение разных моделей/парадигм потоков, как правило, не очень хорошая идея. Например, в Linux потоки OpenMP и C++11 внутренне используют библиотеку Pthreads и поэтому могут мешать друг другу. В любом случае, не могли бы вы уточнить, что вы подразумеваете под утечкой? Как вы это наблюдаете?   -  person Daniel Langr    schedule 15.01.2016
comment
Кроме того, я считаю, что вы должны использовать atomic<bool> calculate{false}; вместо atomic<bool> calculate=false;, поскольку std::atomic удалил конструктор копирования, и это необходимо для инициализации копирования даже в случае оптимизации временного.   -  person Daniel Langr    schedule 15.01.2016
comment
Как правило, не сочетайте OpenMP с ни с чем.   -  person Mysticial    schedule 15.01.2016
comment
@DanielLangr Я вижу в диспетчере задач Windows, что память для программы увеличивается очень быстро.   -  person oLen    schedule 15.01.2016
comment
@DanielLangr Спасибо, я отредактировал инициализацию calculate   -  person oLen    schedule 15.01.2016
comment
@Mystical Я спрашивал об этом в предыдущем посте, но на самом деле не получил хорошего ответа... stackoverflow.com/questions/34316191/   -  person oLen    schedule 15.01.2016
comment
Наблюдение после интенсивного использования OpenMP: никогда не нарушайте одно из неявных ожиданий спецификации. В вашем коде разветвление не происходит в основном потоке и поэтому не определено из POV OpenMP... (см.: stackoverflow.com/questions/13197510/ для одного примера того, что произойдет, если вы нарушаете [неявные] ожидания OpenMP.)   -  person MFH    schedule 16.01.2016


Ответы (1)


Параллельный драйвер MKL (по умолчанию) создан для среды выполнения Intel OpenMP. MSVC компилирует приложения OpenMP в собственной среде выполнения, построенной на основе Win32 ThreadPool API. Оба, скорее всего, не играют хорошо. Безопасно использовать параллельный драйвер MKL только с кодом OpenMP, созданным с помощью компиляторов Intel C/C++/Fortran.

Все будет хорошо, если вы свяжете свой код OpenMP с последовательным драйвером MKL. Таким образом, вы можете вызывать MKL из нескольких потоков одновременно и получать параллельные последовательные экземпляры MKL. Будут ли n параллельные последовательные вызовы MKL медленнее, сравнимы или быстрее, чем однопоточный вызов MKL в n потоках, скорее всего, зависит от типа вычислений и аппаратного обеспечения.

Обратите внимание, что Microsoft больше не поддерживает собственную среду выполнения OpenMP. Поддержка OpenMP в MSVC застряла на версии 2.0, которая более чем на десять лет старше текущей спецификации. Вероятно, есть ошибки во время выполнения (и есть есть ошибки в самой поддержке OpenMP компилятора) и вряд ли они исправятся. Они не хотят, чтобы вы использовали OpenMP, и вместо этого хотели бы, чтобы вы предпочли их собственную библиотеку параллельных шаблонов. Но PPL нельзя переносить на другие платформы (например, Linux), поэтому вам действительно следует использовать Intel Treading Building Blocks (TBB). Если вам нужна качественная поддержка OpenMP под Windows, используйте компилятор Intel или некоторые порты GCC. (Я не работаю в Intel)

person Hristo Iliev    schedule 15.01.2016
comment
PPL является переносимым исходным кодом, поскольку его интерфейс является подмножеством TBB! - person MFH; 16.01.2016
comment
Это просто неправда. Две библиотеки имеют похожие API, пытаясь максимально приблизиться к последовательным алгоритмам STL, но PPL не является подмножеством TBB, и они не полностью совместимы на уровне исходного кода. Непосредственный пример: concurrency::parallel_transform из PPL. Подробнее здесь. - person Hristo Iliev; 17.01.2016
comment
Спасибо за полезный ответ. Я все еще не очень уверен, как я буду со всем этим справляться, поскольку OpenMP до сих пор был действительно практичным для вычислительно тяжелых частей, но, по крайней мере, я начинаю понимать, в чем проблема... Я буду обязательно взгляните на TBB и попытайтесь оценить, стоит ли переходить на него. - person oLen; 18.01.2016