OMP_NUM_THREADS=1 时 #pragma ompatomic 的性能问题

2024-04-18

我观察到我正在编写的 openmp 代码出现了意外的(对我来说!)行为。代码结构如下:

#pragma omp parallel for
for(int i=0;i<N;i++){ 
 // lots of calculations that produce 3 integers i1,i2,i3 and 3 doubles d1,d2,d3 
 #pragma omp atomic 
 J1[i1] += d1;
 #pragma omp atomic
 J2[i2] += d2; 
 #pragma omp atomic
 J3[i3] += d3; 
}

我编译了该代码的三个不同版本:

1) 使用 openmp (-fopenmp)

2)没有openmp

3)使用openmp,但没有3个原子操作(只是作为测试,因为原子操作是必要的)

当我使用环境变量 OMP_NUM_THREADS=1 运行版本 1) 时,我观察到版本 2) 的速度显着下降;而版本 3) 的运行速度与版本 2) 一样快。

我想知道这种行为的原因(为什么即使是单线程,原子操作也会减慢代码速度?!)以及是否可以以版本 1)运行速度一样快的方式编译/重写代码版本 2)。

我在问题末尾附上一个显示上述行为的工作示例。我编译了 1):

g++ -fopenmp -o toy_code toy_code.cpp -std=c++11 -O3

2) with:

g++ -o toy_code_NO_OMP toy_code.cpp -std=c++11 -O3

3) 与:

g++ -fopenmp -o toy_code_NO_ATOMIC toy_code_NO_ATOMIC.cpp -std=c++11 -O3

编译器的版本是gcc版本5.3.1 20160519(Debian 5.3.1-20)。 3个版本的执行时间分别为:

1) 1 分 24 秒

2) 51 秒

3)51秒

预先感谢您的任何建议!

// toy_code.cpp 
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <cmath>
#include <omp.h>
#define Np 1000000
#define N 1000

int main (){
        double* Xp, *Yp, *J,*Jb;
        Xp = new double[Np];
        Yp = new double[Np];  
        J = new double [N*N];
        Jb = new double [N*N];

        for(int i=0;i<N*N;i++){
            J[i]=0.0;
            Jb[i]=0.0;
        }

        for(int i=0;i<Np;i++){
            Xp[i] = rand()*1.0/RAND_MAX - 0.5;
            Yp[i] = rand()*1.0/RAND_MAX - 0.5;
        }

        for(int n=0; n<2000; n++){
        #pragma omp parallel for
        for(int p=0;p<Np;p++){
            double rx = (Xp[p]+0.5)*(N-1);
            double ry = (Yp[p]+0.5)*(N-1);
            int xindex = (int)floor(rx+0.5);
            int yindex = (int)floor(ry+0.5);
            int k;
            k=xindex*N+yindex;

            #pragma omp atomic
            J[k]+=1;
            #pragma omp atomic
            Jb[k]+=1;
         }
         }

        delete[] Xp;
        delete[] Yp;
        delete[] J;
        delete[] Jb;

return 0;
}

如果启用 OpenMP,gcc 必须生成适用于仅在运行时已知的任意数量线程的不同代码。

在这种特殊情况下,看一下输出gcc -S(通过标签稍微缩短)。

没有 OpenMP:

.loc 1 38 0 discriminator 2  # Line 38 is J[k]+=1;
movsd   8(%rsp), %xmm1
cvttsd2si   %xmm0, %edx
cvttsd2si   %xmm1, %eax
movsd   .LC3(%rip), %xmm0
imull   $1000, %eax, %eax
addl    %edx, %eax
cltq
salq    $3, %rax
leaq    0(%r13,%rax), %rdx
.loc 1 40 0 discriminator 2   # Line 40 is Jb[k]+=1;
addq    %r12, %rax
.loc 1 29 0 discriminator 2
cmpq    $8000000, %r15
.loc 1 38 0 discriminator 2
addsd   (%rdx), %xmm0
movsd   %xmm0, (%rdx)
.loc 1 40 0 discriminator 2
movsd   .LC3(%rip), %xmm0
addsd   (%rax), %xmm0
movsd   %xmm0, (%rax)

循环展开使得这变得相当复杂。

With -fopenmp:

movsd   (%rsp), %xmm2
cvttsd2si   %xmm0, %eax
cvttsd2si   %xmm2, %ecx
imull   $1000, %ecx, %ecx
addl    %eax, %ecx
movslq  %ecx, %rcx
salq    $3, %rcx
movq    %rcx, %rsi
addq    16(%rbp), %rsi
movq    (%rsi), %rdx
movsd   8(%rsp), %xmm1
jmp .L4
movq    %rax, %rdx
movq    %rdx, (%rsp)
movq    %rdx, %rax
movsd   (%rsp), %xmm3
addsd   %xmm1, %xmm3
movq    %xmm3, %rdi
lock cmpxchgq   %rdi, (%rsi)
cmpq    %rax, %rdx
jne .L9
.loc 1 40 0
addq    24(%rbp), %rcx
movq    (%rcx), %rdx
jmp .L5
.p2align 4,,10
.p2align 3
movq    %rax, %rdx
movq    %rdx, (%rsp)
movq    %rdx, %rax
movsd   (%rsp), %xmm4
addsd   %xmm1, %xmm4
movq    %xmm4, %rsi
lock cmpxchgq   %rsi, (%rcx)
cmpq    %rax, %rdx
jne .L10
addq    $8, %r12
cmpq    %r12, %rbx
jne .L6

我不会尝试解释或理解这里发生的所有细节,但这对于消息来说不是必需的:编译器必须使用可能更昂贵的不同原子指令,尤其是lock cmpxchgq.

除了这个基本问题之外,OpenMP 可能会以任何可以想象的方式干扰优化器,例如干扰展开。我还看到了一个奇怪的案例,英特尔编译器实际上为 OpenMP 循环生成了更高效的串行代码。

附:认为自己很幸运——情况可能会更糟。如果编译器无法将原子指令映射到硬件指令,则必须使用锁,这会更慢。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

OMP_NUM_THREADS=1 时 #pragma ompatomic 的性能问题 的相关文章

  • C#中如何检测字符串是否为货币

    通常当我需要转换时currency string 如 1200 55 z 或 1 249 到十进制值我这样做 if currencyString Contains z decimal value Decimal Parse dataToCh
  • 处理 LINQ sum 表达式中的 null

    我正在使用 LINQ 查询来查找列的总和 并且在少数情况下该值有可能为空 我现在使用的查询是 int score dbContext domainmaps Where p gt p SchoolId schoolid Sum v gt v
  • .NET 可移植类库中的 .ToShortDateString 发生了什么

    我想知道为什么没有 ToShortDateString在 NET 可移植类库中 我有 2 个项目 Silverlight 和常规 NET 类库 使用相同的代码 并且代码涉及调用 ToShortDateString on a DateTime
  • 当我单击 GridView 项时返回 ImageView 实例

    当我点击GridView项时如何返回ImageView实例 我为 ItemClick 创建自定义绑定事件 public class ItemClickSquareBinding MvxBaseAndroidTargetBinding pri
  • 阅读 Stack Overflow RSS 源

    我正在尝试获取未回答问题的列表the feed https stackoverflow com feeds 但我在阅读时遇到困难 const string RECENT QUESTIONS https stackoverflow com f
  • C++ 在 Vector 中使用不可分配的对象

    我想将对象列表存储在std vector 但对象包含引用且无法分配给 但是 我可以复制构造该对象 我能想到的唯一选择是使用指针来包装对象并在需要分配指针时重新设置指针 但这样做的语法会显着降低可读性 特别是在使用迭代器时 我更喜欢另一种选择
  • 控制台应用程序 .net Core 2.0 的配置

    在 net Core 1 中我们可以这样做 IConfiguration config new ConfigurationBuilder AddJsonFile appsettings json true true Build 这样就可以使
  • C中有const吗?

    这个问题可能很幼稚 但是 有没有constC 中的关键字 从哪个版本开始 之间有任何语义和 或句法差异吗const在 C 和 C 中 C 和 C 之间在语法上没有差异const关键字 除了一个相当晦涩的关键字 在 C 中 自 C99 起 您
  • 通过 mpi 发送 c++ std::vector

    我知道存储一个std vector
  • C#:如何使用 SHOpenFolderAndSelectItems [重复]

    这个问题在这里已经有答案了 有人可以举例说明如何使用 shell 函数吗SH打开文件夹并选择项目 http msdn microsoft com en us library bb762232 VS 85 aspx来自 C 我不太明白如何使用
  • 标准 C 中的 sizeof 与 sizeof()? [复制]

    这个问题在这里已经有答案了 我看到一些直接使用 sizeof 的代码 想知道它是否是标准 C 令我惊讶的是 它运行得很好 这是一个例子 include
  • 如何使用 Clang 查找内存泄漏

    我在我的机器 ubuntu 中安装了 Clang 以便发现我的 C 代码中的内存泄漏 我编写了一个示例代码来检查它的工作情况 如下所示 File hello c for leak detection include
  • 如何从枚举中选择随机值?

    给定 C 中的任意枚举 如何选择随机值 我没有找到这个非常基本的问题 我会在一分钟内发布我的答案作为任何人的参考 但请随意发布你自己的答案 Array values Enum GetValues typeof Bar Random rand
  • 为什么我不能在扩展 List 的类中调用 OrderBy?

    我有一堂课 Deck 其中包含一个名为的方法Shuffle 我正在致力于重构Deck延长List
  • 在 unix 中编译 dhrystone 时出错

    我是使用基准测试和 makefile 的新手 我已经从下面的链接下载了 Dhrystone 基准测试 我正在尝试编译它 但我遇到了奇怪的错误 我尝试解决它 但没有成功 有人可以帮助我运行 dhrystone 基准测试吗 以下是我尝试编译的两
  • 宏观评价[重复]

    这个问题在这里已经有答案了 可能的重复 未定义的行为和序列点 https stackoverflow com questions 4176328 undefined behavior and sequence points 我无法理解以下宏
  • 有没有办法让 VS2010 在我的方法中扩展或收缩 try 块?

    我的代码有很多 try catch finally 块 与我在 VS2010 中的方法不同 除了添加区域之外 我无法在开发时扩展或收缩这些区域来隐藏内容 try vm R vm Qu vm T vm D vm Fil vm Type vm
  • 如何仅更改 DateTime 的日期部分,同时保留时间部分?

    我在代码中使用了很多 DateTime 我想将这些日期时间更改为我的特定日期并保留 时间 1 2012 02 02 06 00 00 gt 2015 12 12 06 00 00 2 2013 02 02 12 00 00 gt 2015
  • Windows 上 libcurl 的静态库[重复]

    这个问题在这里已经有答案了 如何将此库 libcurl 静态链接到 exe 我努力了 disable share enable static 没有帮助 我使用的是MingW32 有没有一种简单的方法来静态链接这个库 这样我的应用程序就不再有
  • 当我读取 500MB FileStream 时出现 OutOfMemoryException

    我使用 Filestream 读取大文件 gt 500 MB 但出现 OutOfMemoryException 任何有关它的解决方案 我的代码是 using var fs3 new FileStream filePath2 FileMode

随机推荐