OpenMP中的atomic和critical之间有什么区别?


Answers:


173

对g_qCount的作用是相同的,但是所做的不同。

OpenMP关键部分是完全通用的-它可以包围任何代码块。但是,您为这种一般性付出了代价,因为每次线程进入和退出关键部分都会产生大量开销(除了序列化的固有成本之外)。

(此外,在OpenMP中,所有未命名的关键部分都被认为是相同的(如果您愿意,所有未命名的关键部分只有一个锁),因此,如果一个线程位于上述一个[unnamed]关键部分中,则没有线程可以输入任何内容。 [未命名]关键部分。您可能会猜到,可以使用命名的关键部分来解决此问题。

原子操作的开销要低得多。如果可用,它将利用提供(例如)原子增量操作的硬件;在那种情况下,在输入/退出代码行时不需要锁定/解锁,它只是执行原子级的增量,而硬件告诉您不会受到干扰。

好处是开销要低得多,并且处于原子操作中的一个线程不会阻止任何即将发生的(不同的)原子操作。缺点是原子支持的操作受限。

当然,无论哪种情况,都会产生序列化的成本。


5
“您可能会失去便携性”-我不确定这是真的。该标准(2.0版),其中的原子操作是允许指定(基本的东西像++*=),而如果他们没有在硬件支持,他们可能会被替换critical部分。
丹·R

@DanRoche:是的,你说得对。我认为这种说法是不正确的,我现在将对其进行纠正。
乔纳森·杜尔西

几天前,我遵循了OpenMP教程,据我了解,两个不同的代码有所不同。这是因为临界区可确保该指令一定时间由一个线程执行,所以结果可能会有所不同,但是该指令可能是:g_qCount = g_qCount + 1; 对于线程1,仅将g_qCount结果仅存储在写缓冲区中而不存储在RAM内存中,而当线程2获取值g_qCount时,它仅在RAM中而不是在写缓冲区中读取该值。原子指令确保指令将数据刷新到内存
Giox79,18年

30

在OpenMP中,所有未命名的关键部分都是互斥的。

关键和原子之间最重要的区别是原子只能保护单个分配,并且可以将其与特定的运算符一起使用。


13
这最好是对先前答案的评论(或编辑)。
凯南2015年

20

关键部分:

  • 确保代码块的序列化。
  • 通过适当使用“名称”标签,可以扩展为序列化块组。

  • 慢点!

原子操作:

  • 快多了!

  • 仅确保特定操作的序列化。


9
但是,这答案是非常可读将是第一个回答的一个伟大的总结
米哈尔Miszczyszyn

7

最快的方法既不是关键也不是原子。具有临界截面的加成大约比简单加成贵200倍,原子加成比简单加成贵25倍。

最快的选择(并非总是适用)是为每个线程提供自己的计数器,并在需要总和时进行减少运算。


2
我不同意您在解释中提到的所有数字。假设x86_64,原子操作将具有一些周期开销(同步缓存行),而这大约需要一个周期。如果否则,您将承担“真正的共享”费用,那么开销是虚无的。关键部分会招致锁的费用。根据是否已采取锁定,开销大约为2条原子指令或两次运行调度程序和休眠时间-通常通常会超过200倍。
克拉斯·范·根德

6

的限制atomic很重要。它们应在OpenMP规范中详细说明。MSDN提供了一个快速备忘单,如果这种情况不会改变,我不会感到惊讶。(Visual Studio 2012从2002年3月开始实施OpenMP。)要引用MSDN:

expression语句必须具有以下形式之一:

xbinop =expr

x++

++x

x--

--x

在前面的表达式中:xlvalue具有标量类型的表达式。expr是标量类型的表达式,它不引用所指定的对象xbinop不是重载运算符,是中的一个+*-/&^|<<,或>>

我建议atomic您在可以的情况下使用,否则将其命名为关键部分。命名它们很重要;您将避免通过这种方式调试麻烦。


1
这还不是全部,我们还有其他高级原子指令,例如:#pragma omp aromic update(或read,upate,write,capture),因此它使我们可以有一些其他有益的发言
–pooria

1

这里已经有很好的解释。但是,我们可以进一步深入。要了解OpenMP中原子关键部分概念之间的核心区别,我们必须首先了解锁的概念。让我们回顾一下为什么需要使用locks

一个并行程序正在由多个线程执行。只有当我们在这些线程之间执行同步时,才会产生确定性的结果。当然,并非总是需要线程之间的同步。我们指的是需要同步的情况。

为了同步多线程程序中的线程,我们将使用lock。当要求一次只限制一个线程访问时,就起作用了。所述概念的实施可以变化从处理器到处理器。让我们从算法的角度了解简单锁的工作方式。

1. Define a variable called lock.
2. For each thread:
   2.1. Read the lock.
   2.2. If lock == 0, lock = 1 and goto 3    // Try to grab the lock
       Else goto 2.1    // Wait until the lock is released
3. Do something...
4. lock = 0    // Release the lock

可以使用以下硬件语言来实现给定算法。我们将假设一个处理器并分析其中的锁行为。对于这种做法,我们假设使用以下处理器之一:MIPSAlphaARMPower

try:    LW R1, lock
        BNEZ R1, try
        ADDI R1, R1, #1
        SW R1, lock

这个程序似乎还可以,但事实并非如此。上面的代码存在先前的问题;同步。让我们找到问题所在。假定lock的初始值为零。如果两个线程运行此代码,则一个线程可能到达SW R1,在另一个线程读取lock变量之前将其锁定。因此,他们俩都认为是免费的。为了解决此问题,提供了另一条指令,而不是简单的LWSW。这称为读取-修改-写入指令。这是一条复杂的指令(由子指令组成),可确保锁获取过程仅由单个指令完成一次线程。与简单的ReadWrite指令相比,Read-Modify-Write的区别在于它使用了不同的Load and Storing方式。它使用LL(链接加载)来加载锁定变量,并使用SC(存储条件)来写入锁定变量。附加的链接寄存器用于确保锁获取过程由单个线程完成。该算法如下。

1. Define a variable called lock.
2. For each thread:
   2.1. Read the lock and put the address of lock variable inside the Link Register.
   2.2. If (lock == 0) and (&lock == Link Register), lock = 1 and reset the Link Register then goto 3    // Try to grab the lock
       Else goto 2.1    // Wait until the lock is released
3. Do something...
4. lock = 0    // Release the lock

重置链接寄存器后,如果另一个线程认为该锁是空闲的,则它将无法再次将递增的值写入该锁。因此,获得了访问变量的并发性。

关键原子之间的核心区别在于:

为什么可以在使用实际变量(对其执行操作)的同时使用锁(新变量)作为锁变量?

使用变量将导致临界区,而将实际变量用作锁将导致原子概念。当我们对实际变量执行大量计算(不止一行)时,关键部分很有用。这是因为,如果这些计算的结果未能写入实际变量,则应重复整个过程以计算结果。与在进入高度计算区域之前等待释放锁相比,这可能导致性能下降。因此,建议您在每次要执行单个计算(x ++,x-,++ x,-x等)并使用时使用原子指令。当密集部分正在执行一个计算复杂性更高的区域时的关键指令。


-5

当您只需要对一条指令启用互斥时,atomic的性能相对较高,这对omp至关重要。


13
这无非是在没有解释的情况下对已接受的答案进行了措辞拙劣的重述。
高性能Mark

-5

原子是单个语句的关键部分,即您锁定一个语句的执行

关键部分是对代码块的锁定

一个好的编译器将以与第一个相同的方式翻译您的第二个代码


那是错误的。请不要谈论您不了解的内容。
jcsahnwaldt恢复莫妮卡
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.