我刚接触到openmp,但几天来一直对此感到困惑,在网上找不到任何答案。希望这里有人能给我解释一下这个奇怪的现象。
我想比较同一程序的顺序版本和并行版本之间的运行时。当我用-O或更高版本编译它们(在gcc-10上)时,并行版本的运行速度比顺序版本(~5x)快得多(但不同级别之间的差异很小)。
但是,当我使用-O0编译两个程序时,情况并非如此。事实上,当使用-O0计算两个版本时,顺序版本甚至会稍微快一些。我试图理解,一些仅在O1和更高版本中启用的优化是否有很大的效果,但却没有成功。
可以肯定的是,用-Os编译比-O0好,但效率远低于-O1和更高级别。
有人注意到类似的事情了吗?有什么解释吗?
谢谢!
====
发布于 2020-11-07 00:01:02
所有循环的核心是这样的:
var += something;在顺序代码中,每个var都是一个本地堆栈变量,使用-O0,行编译为:
; Compute something and place it in RAX
ADD QWORD PTR [RBP-vvv], RAX这里,vvv是以存储在RBP中的地址为根的堆栈帧中var的偏移量。
使用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个共享累加器:xa、xb等。每个累加器长8个字节,在内存中对齐,共有64个字节。考虑到大多数编译器将这些变量放置在内存中的方式,它们很可能在相同的缓存线或两条缓存线中相邻( x86-64上的高速缓存行长为64字节,并作为一个单元进行读写)。当一个线程写入其累加器(例如,线程0更新xa )时,这将使所有其他线程的缓存无效,这些线程的累加器恰好位于同一缓存行中,它们需要从较高级别的缓存甚至主存中重新读取值。这很糟糕。这是如此糟糕,以至于它所造成的减速比通过双指针间接访问累加器要糟糕得多。
-O1改变了什么?它引入了寄存器优化:
register r = *(omp_data->var);
for (a = ...) {
r += something;
}
*(omp_data->var) = r;尽管var是一个共享变量,但OpenMP允许每个线程中暂时不同的内存视图。这允许编译器执行寄存器优化,其中var的值在循环的持续时间内不发生变化。
解决方案是简单地使所有xa、xb等都是私有的。
https://stackoverflow.com/questions/64722158
复制相似问题