结合 OpenMP、Intel MKL 和 MSVC 编译器时出现巨大内存泄漏
Huge memory leak when combining OpenMP, Intel MKL and MSVC compiler
我正在从事一个非常重视性能的相当大的 C++ 项目。因此,它依赖于英特尔 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 MB 内存 (!)。
通过稍微修改上面的代码,泄漏就完全消失了。例如:
- 如果程序没有链接到 MKL 库(这里显然不需要),则没有泄漏。
- 如果我告诉 OpenMP 只使用一个线程,(即我将环境变量
OMP_NUM_THREADS
设置为 1
),则不会发生泄漏。
- 如果我注释掉
#pragma omp parallel
行,就没有泄漏。
- 如果我不停止线程并使用
foo.stop()
和 foo.start()
重新启动线程,则不会发生泄漏。
我是不是做错了什么,还是遗漏了什么?
MKL 的并行(默认)驱动程序是针对 Intel 的 OpenMP 运行时构建的。 MSVC 根据自己的运行时编译 OpenMP 应用程序,该运行时围绕 Win32 线程池 API 构建。两者很可能都玩得不好。只有将并行 MKL 驱动程序与使用 Intel C/C++/Fortran 编译器构建的 OpenMP 代码一起使用才是安全的。
如果你link你的OpenMP代码带有MKL的串口驱动应该没问题。这样,您可以同时从多个线程调用 MKL 并获得 MKL 的并发串行实例。 n 并发串行 MKL 调用是否比 n 线程上的单线程 MKL 调用慢、相当或更快可能取决于类型计算和硬件。
请注意,Microsoft 不再支持他们自己的 OpenMP 运行时。 MSVC 的 OpenMP 支持停留在 2.0 版,比当前规范早十多年。运行时可能存在错误(在编译器的 OpenMP 支持本身中有 bugs)并且这些错误不太可能得到修复。他们不希望您使用 OpenMP,而是希望您更喜欢他们自己的并行模式库。但是 PPL 不可移植到其他平台(例如 Linux),因此您真的应该使用 Intel Treading Building Blocks (TBB)。如果您想要 Windows 下的高质量 OpenMP 支持,请使用 Intel 编译器或某些 GCC 端口。 (我不为英特尔工作)
我正在从事一个非常重视性能的相当大的 C++ 项目。因此,它依赖于英特尔 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 MB 内存 (!)。
通过稍微修改上面的代码,泄漏就完全消失了。例如:
- 如果程序没有链接到 MKL 库(这里显然不需要),则没有泄漏。
- 如果我告诉 OpenMP 只使用一个线程,(即我将环境变量
OMP_NUM_THREADS
设置为1
),则不会发生泄漏。 - 如果我注释掉
#pragma omp parallel
行,就没有泄漏。 - 如果我不停止线程并使用
foo.stop()
和foo.start()
重新启动线程,则不会发生泄漏。
我是不是做错了什么,还是遗漏了什么?
MKL 的并行(默认)驱动程序是针对 Intel 的 OpenMP 运行时构建的。 MSVC 根据自己的运行时编译 OpenMP 应用程序,该运行时围绕 Win32 线程池 API 构建。两者很可能都玩得不好。只有将并行 MKL 驱动程序与使用 Intel C/C++/Fortran 编译器构建的 OpenMP 代码一起使用才是安全的。
如果你link你的OpenMP代码带有MKL的串口驱动应该没问题。这样,您可以同时从多个线程调用 MKL 并获得 MKL 的并发串行实例。 n 并发串行 MKL 调用是否比 n 线程上的单线程 MKL 调用慢、相当或更快可能取决于类型计算和硬件。
请注意,Microsoft 不再支持他们自己的 OpenMP 运行时。 MSVC 的 OpenMP 支持停留在 2.0 版,比当前规范早十多年。运行时可能存在错误(在编译器的 OpenMP 支持本身中有 bugs)并且这些错误不太可能得到修复。他们不希望您使用 OpenMP,而是希望您更喜欢他们自己的并行模式库。但是 PPL 不可移植到其他平台(例如 Linux),因此您真的应该使用 Intel Treading Building Blocks (TBB)。如果您想要 Windows 下的高质量 OpenMP 支持,请使用 Intel 编译器或某些 GCC 端口。 (我不为英特尔工作)