OpenMP中的原子和关键有什么区别?
我可以这样做
#pragma omp atomic
g_qCount++;
但与
不同#pragma omp critical
g_qCount++;
答案 0 :(得分:157)
对g_qCount的影响是相同的,但所做的是不同的。
OpenMP临界区是完全通用的 - 它可以包围任意代码块。但是,每次线程进入和退出临界区时(除了序列化的固有成本之外),都会产生很大的开销,因此需要为此付出代价。
(另外,在OpenMP中,所有未命名的关键部分都被认为是相同的(如果您愿意,所有未命名的关键部分只有一个锁),因此,如果一个线程在上面的一个[未命名]临界区中,则没有线程可以输入任何[未命名的]关键部分。正如您可能猜到的那样,您可以通过使用命名的关键部分来解决这个问题。)
原子操作的开销要低得多。在可用的情况下,它利用提供(比如说)原子增量操作的硬件;在这种情况下,进入/退出代码行时不需要锁定/解锁,它只是硬件告诉你的原子增量不会受到干扰。
好处是开销要低得多,并且原子操作中的一个线程不会阻止任何(不同的)原子操作即将发生。缺点是原子支持的有限操作集。
当然,在任何一种情况下,都会产生序列化的成本。
答案 1 :(得分:26)
在OpenMP中,所有未命名的关键部分都是互斥的。
关键和原子之间最重要的区别是原子只能保护单个赋值,你可以将它用于特定的运算符。
答案 2 :(得分:15)
关键部分:
可以通过正确使用“name”标签扩展到序列化块组。
较慢!
原子操作:
快得多!
仅确保特定操作的序列化。
答案 3 :(得分:6)
最快的方式既不重要也不原子。大约,添加临界部分比简单添加贵200倍,原子添加比简单添加贵25倍。
最快的选项(并不总是适用)是为每个线程提供自己的计数器,并在需要总和时进行减少操作。
答案 4 :(得分:5)
atomic
的限制非常重要。它们应在OpenMP specs上详细说明。 MSDN提供快速备忘单,因为如果不改变我也不会感到惊讶。 (Visual Studio 2012从2002年3月开始实施OpenMP。)引用MSDN:
表达式语句必须具有以下形式之一:
x
binop =expr
x++
++x
x--
--x
在前面的表达式中:
x
是带有标量类型的lvalue
表达式。expr
是一个标量类型的表达式,它不引用x
指定的对象。 binop 不是重载运算符,而是+
,*
,-
,/
,&
,{{1}之一}},^
,|
或<<
。
我建议您尽可能使用>>
,否则命名关键部分。命名它们很重要;你会避免以这种方式调试头痛。
答案 5 :(得分:1)
这里已有很好的解释。但是,我们可以深入一点。要了解OpenMP中 atomic 和关键部分概念之间的核心差异,我们必须首先理解 lock 的概念。让我们来看看为什么我们需要使用 locks 。
多个线程正在执行并行程序。当且仅当我们在这些线程之间执行同步时,才会发生确定性结果。当然,并不总是需要线程之间的同步。我们指的是同步是必要的情况。
为了在多线程程序中同步线程,我们将使用 lock 。当访问需要一次只受一个线程限制时,锁定即可发挥作用。 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
给定的算法可以用硬件语言实现,如下所示。我们将假设一个处理器并分析其中的锁的行为。对于这种做法,我们假设以下处理器之一: MIPS , Alpha , ARM 或电源。
try: LW R1, lock
BNEZ R1, try
ADDI R1, R1, #1
SW R1, lock
这个程序似乎没问题,但事实并非如此。上面的代码遇到了上一个问题; 同步。让我们找到问题所在。假设lock的初始值为零。如果两个线程运行此代码,则可能会在另一个线程读取 lock 变量之前到达 SW R1,锁定。因此,他们都认为 lock 是免费的。 要解决此问题,提供了另一条指令,而不是简单的 LW 和 SW 。它被称为读 - 修改 - 写指令。这是一个复杂的指令(由子指令组成),可确保锁定获取过程一次仅由单个线程完成。与简单的 Read 和 Write 指令相比, Read-Modify-Write 的区别在于它使用了不同的 Loading <方式< / em>和存储。它使用 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
当重置链接寄存器时,如果另一个线程假定锁定是空闲的,则它将无法再次将增加的值写入锁定。因此,获得了访问 lock 变量的并发性。
critical 和 atomic 之间的核心区别来自:
为什么在我们可以使用实际变量(我们对其执行操作)时使用锁(一个新变量)作为锁变量?
对锁使用新变量将导致关键部分,同时使用实际变量作为锁将导致原子概念。当我们在实际变量上执行大量计算(多行)时,临界区很有用。这是因为,如果这些计算的结果未能写入实际变量,则应重复整个过程来计算结果。与在进入高度计算区域之前等待锁定释放相比,这可能导致差的性能。因此,建议每当要执行单个计算(x ++,x - ,++ x, - x等)并使用 critical <时,使用 atomic 指令/ em>指令,当密集部分完成一个计算更复杂的区域时。
答案 6 :(得分:-5)
atomic相对性能更高。类似于omp critical的情况并不正确。
答案 7 :(得分:-5)
atomic是一个单一语句Critical部分,即你锁定一个语句执行
关键部分是对代码块的锁定
一个好的编译器会像第一个代码一样翻译你的第二个代码