在回答一个不同的问题(如何使用openmp对此代码进行并行化?)时,我遇到了Clang的一些低效率的代码生成。
让我们考虑以下简单的代码:
void scale(float* inout, ptrdiff_t n, ptrdiff_t m, ptrdiff_t stride, float value)
{
const float inverse = 1.f / value;
# pragma omp parallel for
for(ptrdiff_t i = 0; i < n; ++i) {
# pragma omp simd
for(ptrdiff_t j = 0; j < m; ++j)
inout[i * stride + j] *= inverse;
}
}你把逆的计算放哪儿了,它重要吗?我已经探讨过的选择:
对于GCC-11,选项1生成最佳代码:一个除法,然后一个内存负载和每个线程的广播。选项2-4都生成基本相同的代码,每个线程执行一次除法.
Clang组件
然而,使用Clang-13的代码却有很大的不同。
选项1:在内部循环中加载冗余内存并广播。它不通过堆栈指针加载,而是浪费一个通用寄存器作为指向常量的指针。如果将代码更改为需要多个常量,Clang将浪费多个GP寄存器。
选项2:与GCC相同的代码模式
选项3:在外部循环的每次迭代中重复一次除法
选项4:重复内部循环中的除法
摘要
似乎Clang的代码生成在从OpenMP循环中提取冗余计算时遇到了一些问题。有趣的是,它似乎不影响数组索引计算。把它从内部循环中拉出来很好。
如果我想要对GCC和Clang都有效的代码,我必须编写如下代码:
void scale(float* inout, ptrdiff_t n, ptrdiff_t m, ptrdiff_t stride, float value)
{
# pragma omp parallel
{
const float inverse = 1.f / value;
# pragma omp for nowait
for(ptrdiff_t i = 0; i < n; ++i) {
# pragma omp simd
for(ptrdiff_t j = 0; j < m; ++j)
inout[i * stride + j] *= inverse;
}
}
}但这是非常冗长的。这在这个代码示例中是一个小麻烦,但是如果您查看上面另一个答案中的代码,它会变得非常糟糕(尤其是GP寄存器浪费),严重影响性能。
总之,我是不是漏掉了什么?我是否应该以不同的方式编写循环,以确保Clang和GCC都有好的代码?
补充信息
下面是一个允许简单测试的代码版本,下面是一个螺栓连接
#include <cstddef>
// using std::ptrdiff_t
#define CONST_LOCATION 1
void scale(float* inout, std::ptrdiff_t n, std::ptrdiff_t m, std::ptrdiff_t stride,
float value)
{
# if CONST_LOCATION == 1
/*
* Clang-13.0.1: Redundant broadcast from memory in inner loop.
* Wastes GP register for pointer to constant
* GCC-11.2: Optimal
*/
const float inv = 1.f / value;
#endif
# pragma omp parallel
{
# if CONST_LOCATION == 2
/*
* Clang: Redundant computation in outer loop setup. Otherwise optimal
* GCC: Same as Clang
*/
const float inv = 1.f / value;
# endif
# pragma omp for nowait
for(std::ptrdiff_t i = 0; i < n; ++i) {
# if CONST_LOCATION == 3
/*
* Clang: Redundant computation in inner loop setup!
* GCC: Same as 2
*/
const float inv = 1.f / value;
# endif
# pragma omp simd
for(std::ptrdiff_t j = 0; j < m; ++j) {
# if CONST_LOCATION == 4
/*
* Clang: Redundant computation in inner loop!
* GCC: Same as 2
*/
const float inv = 1.f / value;
# endif
inout[i*stride + j] *= inv;
}
}
}
}用-O3 -mavx2 -mfma -fopenmp进行测试,以获得合理的通用、现代编译。
发布于 2022-02-28 15:47:04
要回答我自己,是内部循环中的pragma omp simd混淆了clang的代码生成。这是令人遗憾的,因为在某些情况下,它确实对某些编译器产生了积极影响。
https://stackoverflow.com/questions/71294894
复制相似问题