强制 OpenMP 不在每个线程中缓存大对象
Posted
技术标签:
【中文标题】强制 OpenMP 不在每个线程中缓存大对象【英文标题】:Force OpenMP to not cache a large object in each thread 【发布时间】:2020-01-07 22:47:07 【问题描述】:我正在编写一个带有循环的 C++ 程序,我尝试使用 OpenMP 进行并行化。我正在编写的循环具有以下结构:
#pragma omp parallel for
for (int i = 0; i < N; i++)
result[i] = work_func(left[i], right[i], largeObject);
largeObject
参数被标记为常量引用。我的问题是,当我从单个线程转移到多个线程(约 40 个)时,内存使用量会急剧增加。 left
和 right
参数都很小,这意味着即使将它们完全复制到所有线程也不会导致内存增加。
我想告诉 OpenMP 不要将 largeObject
复制到所有线程本地缓存,而是强制它使用单个全局副本。有没有办法做到这一点?这似乎与 OpenMP 性能问题更常见的无错误共享优化背道而驰。我不太关心运行时减速,而不是这个程序的大量内存开销。
谢谢!
【问题讨论】:
This question 似乎遇到了类似的问题,但答案似乎没有帮助... 你确定work_func
内部没有进行复制,或者为其他东西分配大块内存吗?
嗯,这将是一个合理的解释,但我只是仔细检查了work_func
,它调用的所有函数都将largeObject
作为常量引用。我会继续寻找,但我认为这不是问题
此外,该函数确实分配了少量内存,但即使所有线程中的所有这些分配都不会考虑内存增加。
您是否尝试过为 OpenMP 内存空间(在 2.11.1,Tab.2.8 中)+ omp_init_allocator(在 3.7.2 中)微调内存管理指令(在 API 规范中:第 2.11 段)。此外,work_func() 的实际代码会在这个主题上提供更多信息,具体取决于如何访问/计算 largeObject 数据。
【参考方案1】:
int
const
largeObject
-declaration 指令应提倡编译阶段以避免任何共享附加机制和/或主要不可变对象的同步策略(因为没有出现任何需要访问声明的不可变const largeObject
的写入尝试的竞争条件。使用volatile
正如@Gilles 所提到的,指令使用另一种编译器注入的值访问策略机制,该机制与 OpenMP 没有直接关系,但受到相应的 omp
-section(s) 的尊重。
#include <iostream>
#include <omp.h>
#define anIndeedLargeSIZE 2
int main()
int largeObject[anIndeedLargeSIZE] = 0;
#pragma omp const largeObject
std::cout << "largeObject address " << largeObject << std::endl;
#pragma omp parallel for num_threads(2)
for (int i = 0; i < 2; i++)
int tid = omp_get_thread_num();
std::cout << "tid: " << tid << " :: " << largeObject << std::endl;
if (i == tid)
// largeObject[i] = tid; // const .... un-mutable mode
std::cout << "tid: " << tid << " :: now reading and using a const largeObject[" << (int)i << "] == " << largeObject[i] << std::endl;
std::cout << "largeObject processing FINISHED." << std::endl;
return 0;
请测试确实大尺寸的内存分配副作用,IDE 原型代码在Godbolt site IDE 测试不公平(用于进一步实验和扩展分析的完整 MCVE 代码完全存在编译器选项使用 there ) 作为 OpenMP API 文档警告,实际行为是“特定于实现”。
(base) Wed Jan 08 00:00:00 @64FX:~/$ g++ -o largeObject_const_OMP -O3 -fopenmp largeObject_const_OMP.c
largeObject_const_OMP.c: In function ‘int main()’:
largeObject_const_OMP.c:65:30: error: expected ‘#pragma omp’ clause before ‘const’
#pragma omp parallel for const (largeObject) num_threads(2)
<--------------------code-revised-as-desired-by-parsing-error:65:30:expected ‘#pragma omp’ clause ADDED before ‘const’-->
(base) Wed Jan 08 00:00:00 @64FX:~/$ g++ -o largeObject_const_OMP -O3 -fopenmp largeObject_const_OMP.c
<--------------------no-error-message|warning-from-parse|compile|link-phases-HERE->
(base) Wed Jan 08 00:00:00 @64FX:~/$ ./largeObject_const_OMP
largeObject address 0x7fff81b97d58
tid: tid: 0 :: 10x7fff81b97d58 ::
tid: 0 :: now reading and using a const largeObject[0] == 0
0x7fff81b97d58
tid: 1 :: now reading and using a const largeObject[1] == 0
largeObject processing FINISHED.
访问int const
v/s int volatile
largeObject
:
#include <iostream> // >>> https://gcc.godbolt.org/z/NRQSQ_
#include <omp.h> // >>> https://***.com/questions/59637163/force-openmp-to-not-cache-a-large-object-in-each-thread/59638455?noredirect=1#comment105445758_59638455
#include <chrono>
#include <thread>
#define anIndeedLargeSIZE 2
int main()
//
int const largeObject[anIndeedLargeSIZE] = 0;
// #pragma omp const largeObject // largeObject_const_OMP.c:46:0: warning: ignoring #pragma omp const [-Wunknown-pragmas]
std::cout << "int const largeObject address[_" << largeObject << "_]" << std::endl;
#pragma omp parallel for num_threads(2)
for (int i = 0; i < 2; i++)
int tid = omp_get_thread_num();
std::this_thread::sleep_for( std::chrono::milliseconds( 100 * tid ) );
std::cout << "tid: " << (int)tid << " ::[_" << largeObject << "_]" << std::endl;
if (i == tid)
// largeObject[i] = tid; // const .... un-mutable mode
std::cout << "tid: " << (int)tid << " :: now reading and using an int const largeObject[" << (int)i << "] == " << largeObject[i] << std::endl;
std::cout << "int const largeObject[] processing FINISHED." << std::endl;
/*////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
>>> ~/$ ./largeObject_const_OMP
* int const largeObject address[_0x7fff3ed0db28_]
* tid: 0 ::[_0x7fff3ed0db28_]
* tid: 0 :: now reading and using an int const largeObject[0] == 0
* tid: 1 ::[_0x7fff3ed0db28_]
* tid: 1 :: now reading and using an int const largeObject[1] == 0
* int const largeObject[] processing FINISHED.
*
* */
/*
int volatile largeObject[anIndeedLargeSIZE] = 0;
std::cout << "int volatile largeObject address[_" << largeObject << "_]" << std::endl;
#pragma omp parallel for num_threads(2)
for (int i = 0; i < 2; i++)
int tid = omp_get_thread_num();
std::this_thread::sleep_for( std::chrono::milliseconds( 100 * tid ) );
std::cout << "tid: " << (int)tid << " ::[_" << largeObject << "_]" << std::endl;
if (i == tid)
// largeObject[i] = tid; // const .... un-mutable mode
std::cout << "tid: " << (int)tid << " :: now reading and using an int volatile largeObject[" << (int)i << "] == " << largeObject[i] << std::endl;
std::cout << "int volatile largeObject[] processing FINISHED." << std::endl;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
>>> ~/$ ./largeObject_const_OMP
* int volatile largeObject address[_1_]
* tid: 0 ::[_1_]
* tid: 0 :: now reading and using an int volatile largeObject[0] == 0
* tid: 1 ::[_1_]
* tid: 1 :: now reading and using an int volatile largeObject[1] == 0
* int volatile largeObject[] processing FINISHED.
* */
return 0;
【讨论】:
从未听说过这个omp const
指令。在the OpenMP standard 上没有找到它,GCC 9.2.0 和 Intel 编译器 2019u5 都无法识别它...const.cc:8: warning: ignoring #pragma omp const
你确定吗?
@Gilles Fact a) g++ 编译没有反对任何抱怨或警告(上面添加的输出)事实 b) OpenMP v5.0 Nov-2018 文档,[第 271 页](第 26+ 行)状态 “具有 const 限定类型且没有可变的变量成员可能会列在 firstprivate 子句中,即使它们是静态数据成员”,这是我们不希望的(对于 firstprivate 内存影响,但使用了上述符号的其余部分,编译按照 sub a)).
你用-Wunknown-pragmas
尝试过g++吗?默认情况下,GCC 不会发出未知的 pragma 信号。
gcc.godbolt.org/z/I36Ght 这是您的链接,仅将 -Wunknown-pragmas
添加到编译器选项中确实给出了 #pragma omp const
未知并因此被忽略的消息。
@Gilles 是的,可以确认 g++ 警告。感谢您的关心。 int const largeObject address[0x7fff3ed0db28] 行为和要访问的值的地址与上面相同,int volatile largeObject address[1 ] 访问此处 volatile 值的引用地址已更改为 [1]。这对于真正的大型 BLOB 将如何工作?以上是关于强制 OpenMP 不在每个线程中缓存大对象的主要内容,如果未能解决你的问题,请参考以下文章
如果我编写一段代码,其中每个线程修改数组的完全不同部分,那会保持缓存一致性吗?