OpenMP 的效率与优化级别

Efficiency of OpenMP vs optimisation levels

我是openmp的新手,为此困惑了几天,在网上找不到任何答案。希望这里有人能给我解释一下这个奇怪的现象。

我想比较同一程序的顺序版本和并行版本的运行时间。当我用 -O 或更高版本编译它们(在 gcc-10 上)时,并行版本比顺序版本(~5x)运行得快得多(但不同级别之间的差异非常小)。

但是,当我使用-O0 编译这两个程序时,情况并非如此。事实上,当使用 -O0 计算两个版本时,顺序版本甚至更快。我试图了解仅在 O1 及更高版本中启用的某些优化是否具有实质性效果,但运气不佳。

郑重声明,使用 -Os 编译比 -O0 好,但效率远低于 -O1 及更高版本。

有没有人注意到类似的事情?对此有解释吗?

谢谢!

====

以下是 c 文件的链接:sequential code, parallel code

你所有循环的核心是这样的:

var += something;

在顺序代码中,每个 var 都是一个局部堆栈变量,-O0 行编译为:

; Compute something and place it in RAX
ADD QWORD PTR [RBP-vvv], RAX

这里vvvvar在以RBP中存储的地址为根的堆栈帧中的偏移量。

使用 OpenMP,对源代码进行某些转换,相同的表达式变为:

*(omp_data->var) = *(omp_data->var) + something;

其中 omp_data 是指向结构的指针,该结构包含指向并行区域中使用的共享变量的指针。这编译为:

; Compute something and store it in RAX
MOV RDX, QWORD PTR [RBP-ooo]  ; Fetch omp_data pointer
MOV RDX, QWORD PTR [RDX]      ; Fetch *(omp_data->var)
ADD RDX, RAX
MOV RAX, QWORD PTR [RBP-ooo]  ; Fetch omp_data pointer
MOV QWORD PTR [RAX], RDX      ; Assign to *(omp_data->var)

这是并行代码较慢的第一个原因 - 递增 var 的简单操作涉及更多的内存访问。

第二个,实际上更有力的原因是虚假分享。您有 8 个共享累加器:xaxb 等。每个长 8 个字节,并在内存中对齐,总共 64 个字节。鉴于大多数编译器如何将此类变量放置在内存中,它们很可能最终在同一缓存行或两个缓存行中彼此相邻(x86-64 上的缓存行长 64 字节,并且作为一个单元读取和写入).当一个线程写入其累加器时,例如,线程 0 更新 xa,这会使累加器恰好位于同一缓存行中的所有其他线程的缓存无效,并且它们需要 re-read 来自上层高速缓存甚至主存。这是不好的。这太糟糕了,它导致的减速比必须通过双指针间接访问累加器更糟糕。

-O1 改变了什么?它引入了寄存器优化:

register r = *(omp_data->var);
for (a = ...) {
   r += something;
}
*(omp_data->var) = r;

尽管 var 是一个共享变量,但 OpenMP 允许在每个线程中临时使用不同的内存视图。这允许编译器执行寄存器优化,其中 var 的值在循环期间不会改变。

解决方案是将所有 xaxb 等设为私有。