并行for循环 vs omp simd:何时使用每种方法?

68

OpenMP 4.0 引入了一个名为“omp simd”的新构造。相比旧的“parallel for”,使用此构造的好处是什么?在哪些情况下各自都更好地选择使用?

编辑: 这里有一份与SIMD指令相关的 论文


OpenMP在3.0版本之前是SIMD的,但随后放弃了这个概念。我猜新的编译指示是为了向后兼容依赖于某些SIMD方面的旧代码。那么OpenMP文档不应该对此有所说明吗? - Andreas Grapentin
5
不是“parallel simd”,而是使用“parallel”或“simd”,这暗示了它们之间的区别。请参见下文。 - Jonathan Dursi
@JD:那是一个打字错误,当然已经修复了,谢谢。 - zr.
9
OpenMP直到3.0版本都没有SIMD功能,只有线程功能。在CPU方面:线程(OMP <= 3.0,parallel for)是将线程/分布分配到核心中,SIMD(OMP >= 4.0,simd)是向量/AVX/分配在一个核心中。这仅作为未来读者的参考。 - Flamefire
3个回答

56
一个简单的答案:
OpenMP 只用于利用多核处理器的多个线程。这个新的 simd 扩展允许您明确地使用现代 CPU 上的 SIMD 指令,例如 Intel 的 AVX/SSE 和 ARM 的 NEON。
(请注意,按设计,一个 SIMD 指令在单个线程和单个核上执行。但是,对于 GPGPU,SIMD 的含义可以相当扩展。但是,我认为您不需要考虑 OpenMP 4.0 的 GPGPU。)
因此,一旦您了解了 SIMD 指令,就可以使用这个新的构造。
在现代CPU中,大致有三种类型的并行性:(1)指令级并行性(ILP),(2)线程级并行性(TLP),以及(3)SIMD指令(我们可以称之为矢量级或类似)。
ILP由你的乱序CPU或编译器自动完成。你可以使用OpenMP的parallel for和其他线程库来利用TLP。那么,SIMD呢?Intrinsics是一种使用它们的方式(以及编译器的自动向量化)。OpenMP的simd是一种使用SIMD的新方法。
以一个非常简单的例子为例:
for (int i = 0; i < N; ++i)
  A[i] = B[i] + C[i];

上述代码计算了两个N维向量的总和。正如您可以轻松地看到的那样,在数组A[]上没有(循环承载)数据依赖性。这个循环是令人尴尬地并行的。
有多种方法可以并行化此循环。例如,直到OpenMP 4.0,只能使用parallel for结构并行化此循环。每个线程将在多个核心上执行N/#thread次迭代。
然而,您可能认为对于这样简单的加法使用多个线程是一种过度杀伤。这就是为什么有向量化,它主要由SIMD指令实现。
使用SIMD将像这样:
for (int i = 0; i < N/8; ++i)
  VECTOR_ADD(A + i, B + i, C + i);

这段代码假设(1)SIMD指令(VECTOR_ADD)是256位或8路(8 * 32位);和(2)N是8的倍数。
8路SIMD指令意味着一个向量中的8个项目可以在单个机器指令中执行。请注意,英特尔最新的AVX提供了这样的8路(32位*8 = 256位)向量指令。
在SIMD中,您仍然使用单个核心(再次强调,这仅适用于传统CPU,而不是GPU)。但是,您可以使用硬件中的隐藏并行性。现代CPU为SIMD指令专门分配硬件资源,其中每个SIMD lane可以并行执行。
您可以同时使用线程级并行性。上面的示例可以通过parallel for进一步并行化。
(但是,我怀疑有多少循环可以真正转换为SIMD化的循环。OpenMP 4.0规范似乎有点不清楚。因此,实际性能和实际限制将取决于实际编译器的实现。)
总之,simd 构造允许您使用 SIMD 指令,进而可以利用更多的并行性和线程级并行性。然而,我认为实际的实现是很重要的。

2
但是,我认为你不需要考虑OpenMP 4.0的GPGPU。事实上,有一个提议支持在OpenMP 4.0中使用GPGPU(和其他类型的加速器),但它在一个单独的[TR]中,而不是主文本的一部分(我认为他们这样做是因为该文本没有足够的精细度,以便被包含在主要审查文本中,在SC'12会议举行时)。 - Hristo Iliev
"simd 构造允许您使用 SIMD 指令" 错误地暗示不使用 simd 构造将禁止使用 SIMD 指令。 - Jeff Hammond

52

链接的标准相对清晰(p 13,第19和20行)

当任何线程遇到 simd 构造时,与构造相关联的循环迭代可以由线程可用的 SIMD lane 执行。

SIMD 是一个子线程的东西。为了更具体,您可以想象在 CPU 上使用 simd 指令来特别请求同一 线程 中单个循环迭代块的向量化。它以一种平台无关的方式展示了单个多核处理器中存在的多层并行性。例如我们可以参考这篇英特尔博客文章中的讨论(连同加速器部分)。

所以基本上,您需要使用 omp parallel 来将工作分配到不同的线程中,然后可以迁移到多个内核; 并且您需要在其中的紧密循环周围使用 omp simd 来利用每个内核中的向量管道(例如)。通常情况下,omp parallel 会放在“外面”,处理更粗粒度的并行分配工作,omp simd 则在其中的紧密循环周围使用以利用细粒度并行性。


2
编译器在并行区域中不需要将simd优化条件化为simd子句的存在。我熟悉的编译器继续支持嵌套循环、并行外层、向量内层,与之前的方式相同。
过去,OpenMP指令通常被认为是防止涉及外部并行化循环(具有折叠子句的多个循环)的循环切换优化。这在一些编译器中似乎已经改变了。 OpenMP 4打开了新的可能性,包括通过一种类似于条带挖掘的方法来优化具有非可向量化内部循环的并行外部循环,当设置omp parallel do [for] simd时。ifort有时会报告它作为外部循环向量化,即使没有使用simd子句。然后可以为比omp parallel do simd更少的线程进行优化,后者似乎需要比simd矢量宽度更多的线程才能获得回报。这样的区别可能会被推断出来,因为没有simd子句,编译器隐含地要求优化例如100或300的循环计数,而simd子句请求无条件的simd优化。 gcc 4.9 omp parallel for simd在我有一个24核平台时看起来非常有效。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接