C++ openmp嵌套循环处理性能 请考虑以下代码: void process(int N, int K, const vector<int>& data) { #pragma omp parallel for for(int i = 0; i < data.size(); ++i) { //perform some processing based on data, N and K } } void solve(int N, int K, const vector<int>& data) { for(int i = 0; i < N; ++i) { for(int j = 0; j < K; ++j) { process(N, K, data); } } }

C++ openmp嵌套循环处理性能 请考虑以下代码: void process(int N, int K, const vector<int>& data) { #pragma omp parallel for for(int i = 0; i < data.size(); ++i) { //perform some processing based on data, N and K } } void solve(int N, int K, const vector<int>& data) { for(int i = 0; i < N; ++i) { for(int j = 0; j < K; ++j) { process(N, K, data); } } },c++,performance,openmp,C++,Performance,Openmp,正如您在被动模式中看到的,当N*K相当大时,时间要大得多 或者其他解决这个问题的方法 当将计算分配到线程时,您希望拥有尽可能大的数据块和尽可能少的同步。在您的示例中,您应该将最外层的循环并行化。在您的示例中,不清楚过程是否修改数据。它作为非常量传递,但假设它未被修改,我希望它的性能会更好: void solve(int N, int K, vector<int>& data) { #pragma omp parallel for for(int i = 0;

正如您在被动模式中看到的,当N*K相当大时,时间要大得多

或者其他解决这个问题的方法

当将计算分配到线程时,您希望拥有尽可能大的数据块和尽可能少的同步。在您的示例中,您应该将最外层的循环并行化。在您的示例中,不清楚
过程是否修改
数据。它作为非常量传递,但假设它未被修改,我希望它的性能会更好:

void solve(int N, int K, vector<int>& data)
{
    #pragma omp parallel for
    for(int i = 0; i < N; ++i)
    {
        for(int j = 0; j < K; ++j)
        {
            process(N, K, data);
        }
    } // <-- threads have to wait here until all are finished
}
void solve(int N、int K、向量和数据)
{
#pragma-omp并行
对于(int i=0;i}//基于您的MCVE,我做了一些测试,我相信您的代码编写方式存在一些问题

  • 您正在使用
    std::atomic
    作为累积结果的计数器的类型,并将其增加(结果是以原子方式增加)。虽然从功能角度来看这是正确的,但这不是一种非常有效的方法。将计数器更改为简单的
    int
    ,并将其声明为
    减少(+:cnt)
    并行
    区域中的
    要好得多
  • 您正在
    N
    x
  • K
    循环中创建
    parallel
    区域。向上移动
    parallel
    指令可能会有所帮助。然后,您以前的
    #pragma omp parallel for
    指令将成为孤立的
    #pragma omp for
    指令 因此,我对这些想法进行了一些实验,下面是我现在得到的(使用4个线程,因为我的机器上有4个内核):

    -您的被动策略版本:

    Large N*K
    Time: 74.4741 ms        Threads: 4 N: 100 K: 100
    Time: 40.2336 ms        Threads: 1 N: 100 K: 100
    
    Small N*K
    Time: 0.151747 ms       Threads: 4 N: 1 K: 1
    Time: 0.395791 ms       Threads: 1 N: 1 K: 1
    
    Large N*K
    Time: 35.1184 ms        Threads: 4 N: 100 K: 100
    Time: 7.932 ms      Threads: 1 N: 100 K: 100
    
    Small N*K
    Time: 0.040216 ms       Threads: 4 N: 1 K: 1
    Time: 0.082633 ms       Threads: 1 N: 1 K: 1
    
    Large N*K
    Time: 5.30402 ms        Threads: 4 N: 100 K: 100
    Time: 9.57645 ms        Threads: 1 N: 100 K: 100
    
    Small N*K
    Time: 0.028136 ms       Threads: 4 N: 1 K: 1
    Time: 0.094375 ms       Threads: 1 N: 1 K: 1
    
    -我的被动策略版本:

    Large N*K
    Time: 74.4741 ms        Threads: 4 N: 100 K: 100
    Time: 40.2336 ms        Threads: 1 N: 100 K: 100
    
    Small N*K
    Time: 0.151747 ms       Threads: 4 N: 1 K: 1
    Time: 0.395791 ms       Threads: 1 N: 1 K: 1
    
    Large N*K
    Time: 35.1184 ms        Threads: 4 N: 100 K: 100
    Time: 7.932 ms      Threads: 1 N: 100 K: 100
    
    Small N*K
    Time: 0.040216 ms       Threads: 4 N: 1 K: 1
    Time: 0.082633 ms       Threads: 1 N: 1 K: 1
    
    Large N*K
    Time: 5.30402 ms        Threads: 4 N: 100 K: 100
    Time: 9.57645 ms        Threads: 1 N: 100 K: 100
    
    Small N*K
    Time: 0.028136 ms       Threads: 4 N: 1 K: 1
    Time: 0.094375 ms       Threads: 1 N: 1 K: 1
    
    -您的版本具有活动策略

    Large N*K
    Time: 16.3105 ms        Threads: 4 N: 100 K: 100
    Time: 44.4862 ms        Threads: 1 N: 100 K: 100
    
    Small N*K
    Time: 0.110355 ms       Threads: 4 N: 1 K: 1
    Time: 0.427118 ms       Threads: 1 N: 1 K: 1
    
    -我的主动策略版本:

    Large N*K
    Time: 74.4741 ms        Threads: 4 N: 100 K: 100
    Time: 40.2336 ms        Threads: 1 N: 100 K: 100
    
    Small N*K
    Time: 0.151747 ms       Threads: 4 N: 1 K: 1
    Time: 0.395791 ms       Threads: 1 N: 1 K: 1
    
    Large N*K
    Time: 35.1184 ms        Threads: 4 N: 100 K: 100
    Time: 7.932 ms      Threads: 1 N: 100 K: 100
    
    Small N*K
    Time: 0.040216 ms       Threads: 4 N: 1 K: 1
    Time: 0.082633 ms       Threads: 1 N: 1 K: 1
    
    Large N*K
    Time: 5.30402 ms        Threads: 4 N: 100 K: 100
    Time: 9.57645 ms        Threads: 1 N: 100 K: 100
    
    Small N*K
    Time: 0.028136 ms       Threads: 4 N: 1 K: 1
    Time: 0.094375 ms       Threads: 1 N: 1 K: 1
    
    由此我可以说:

  • 删除
    std:atomic
    和使用
    还原(+)
    确实会产生重大影响,应继续进行
  • 如果等待策略必须是被动的,那么使用多线程路由没有任何意义,因为在该配置中,单线程版本总是比多线程版本快
  • 为了便于记录,以下是修改后的零件的外观:

    int cnt;
    
    void process(int a, int b, std::vector<int>& d)
    {
        #pragma omp for reduction(+:cnt)
        for (int i = 0; i < d.size(); ++i)
        {
            //sample operation
            if (d[i] > a + b)
                ++cnt;
        }
    }
    
    void solve(int N, int K, std::vector<int>& d)
    {
        #pragma omp parallel
        for (int i = 0; i < N; ++i)
        {
            for (int j = 0; j < K; ++j)
            {
                process(i, j, d);
            }
        }
    }
    
    int-cnt;
    无效流程(int a、int b、std::vector&d)
    {
    #用于还原的pragma omp(+:cnt)
    对于(int i=0;ia+b)
    ++碳纳米管;
    }
    }
    void solve(int N,int K,std::vector&d)
    {
    #pragma-omp并行
    对于(int i=0;i
    谢谢,我已将数据标记为常量。我同意您的说法,但在这种情况下,N和K的大小通常为1(或小于线程数)。我不希望在“进程”中丢失omp优化在这种情况下起作用。@AdamF如果你得到答案后不改变你的问题,我会更喜欢。现在我的答案已经过时了,必须修正它。无论如何,然后制作两个不同版本的
    solve
    ,调用不同版本的
    process
    ,一个是并行
    solve
    ,一个是顺序
    process
    ,另一个是e反过来,然后根据大小选择一个或另一个。这将是一个有趣的练习,看看转折点在哪里。我有点嫉妒;)在这种情况下,我可以完全删除N和K上的循环,并在
    过程中
    迭代0..data.size()*N*K.但这会让代码变得丑陋,维护起来更困难,然后我还需要正确计算索引。@AdamF你写的东西在你发布的代码中并不明显。如果你能将循环展平为一个循环,你应该从一开始就做到这一点。我也看不出这会导致丑陋或任何困难在维护代码时,计算索引有什么问题?在
    solve()
    函数的
    i
    循环之前设置
    #pragma omp parallel
    ,在
    进程()的
    i
    循环之前设置孤
    #pragma omp for
    指令
    函数是否足以解决您的问题?@Gilles我已经尝试过这种方法,但是所有线程都使用相同的参数N,K执行了多次
    进程
    。(可能我用错了这个语句。)请您提供一个@Gilles,您是对的,我在流程循环中使用了
    #pragma omp parallel for
    。应该有
    pragma omp for
    。这样结果是正确的,算法速度更快。(不如活动选项快,但值得在代码中应用。)这正是我所需要的。在我的实际情况中,我不能使用reduce。但是可以使用
    #pragma omp for reduce(+)nowait
    进一步优化您的方法。在这种情况下,
    nowait
    对大N*K也有很大的改进。无论如何,一切都基于您的初始解决方案。谢谢!