强制 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 个)时,内存使用量会急剧增加。 leftright 参数都很小,这意味着即使将它们完全复制到所有线程也不会导致内存增加。

我想告诉 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 不在每个线程中缓存大对象的主要内容,如果未能解决你的问题,请参考以下文章

在openmp中使用不同线程组装矢量时缩放不良

如果我编写一段代码,其中每个线程修改数组的完全不同部分,那会保持缓存一致性吗?

OpenMP 不在不同的线程中运行这个 for 循环,我该如何修复它

如何使用多线程处理缓存的数据结构(例如 openmp)

MySQL表对象缓存

c++ openmp中的线程