OpenMP的效率与优化级别

问题描述

我是openmp的新手,但是几天来我一直对此感到困惑,无法在网上找到任何答案。希望这里有人可以向我解释这个奇怪的现象。

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

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

从记录来看,使用-Os进行编译比-O0更好,但效率远低于-O1及更高版本。

有人注意到类似的东西吗?对此有解释吗?

谢谢!

====

以下是指向c文件链接sequential codeparallel 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,这会使所有其他线程的累加器恰好位于同一高速缓存行中,从而使它们的高速缓存无效,并且它们需要从上级重新读取该值缓存甚至主存储器。这不好。这太糟糕了,以至于造成的减速比必须通过双指针间接访问累加器要糟糕得多。

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

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

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

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