我试图理解OpenMP打破循环向量化的概念原因。此外,任何解决此问题的建议都会有所帮助。我正在考虑手动并行化这个来解决这个问题,但这肯定不会很优雅,导致大量的代码膨胀,因为我的代码包含几个这样的部分,适合于矢量化和并行化。
我正在使用
Microsoft(R)C / C ++优化编译器版本17.00.60315.1 for x64
使用OpenMP:
info C5002:由于原因'502'
,循环未向量化
没有OpenMP:
info C5001:循环矢量化
VS vectorization page表示在以下情况发生此错误:
感应变量以某种方式步进,而不是简单的+1
我可以强制它步入步幅1吗?
循环
#pragma omp parallel for
for (int j = 0; j < H*W; j++)//A,B,C,D,IN are __restricted
{
float Gs = D[j]-B[j];
float Gc = A[j]-C[j];
in[j]=atan2f(Gs,Gc);
}
尽力而为(?)
#pragma omp parallel
{// This seems to vectorize, but it still requires quite a lot of boiler code
int middle = H*W/2;
#pragma omp sections nowait
{
#pragma omp section
for (int j = 0; j < middle; j++)
{
float Gs = D[j]-B[j];
float Gc = A[j]-C[j];
in[j]=atan2f(Gs,Gc);
}
#pragma omp section
for (int j = middle; j < H*W; j++)
{
float Gs = D[j]-B[j];
float Gc = A[j]-C[j];
in[j]=atan2f(Gs,Gc);
}
}
}
答案 0 :(得分:2)
我建议您手动进行矢量化。一个原因是自动矢量化似乎不能很好地处理循环依赖(循环展开)。
为了避免代码臃肿和神秘的内在函数,我使用了Agner Fog的vectorclass。根据我的经验,它与使用内在函数一样快,它会自动利用SSE2-AVX2(AVX2在Intel仿真器上测试),具体取决于您的编译方式。我使用在SSE2上运行到AVX2的vectorclass编写了GEMM代码,当我在带有AVX的系统上运行时,我的代码已经比仅使用SSE的Eigen快。这是你使用vectorclass的函数(我没有尝试展开循环)。
#include "omp.h"
#include "math.h"
#include "vectorclass.h"
#include "vectormath.h"
void loop(const int H, const int W, const int outer_stride, float *A, float *B, float *C, float *D, float* in) {
#pragma omp parallel for
for (int j = 0; j < H*W; j+=8)//A,B,C,D,IN are __restricted, W*H must be a multiple of 8
{
Vec8f Gs = Vec8f().load(&D[j]) - Vec8f().load(&B[j]);
Vec8f Gc = Vec8f().load(&A[j]) - Vec8f().load(&C[j]);
Vec8f invec = atan(Gs, Gc);
invec.store(&in[j]);
}
}
自己进行矢量化时,必须注意数组边界。在上面的函数中,H W需要是8的倍数。有几个解决方案,但最简单和最有效的解决方案是使数组(A,B,C,D,in)更大(最多7个浮点数更大)如果需要是8的倍数。但是,另一个解决方案是使用下面的代码,它不需要W H是8的倍数,但它不是很漂亮。
#define ROUND_DOWN(x, s) ((x) & ~((s)-1))
void loop_fix(const int H, const int W, const int outer_stride, float *A, float *B, float *C, float *D, float* in) {
#pragma omp parallel for
for (int j = 0; j < ROUND_DOWN(H*W,8); j+=8)//A,B,C,D,IN are __restricted
{
Vec8f Gs = Vec8f().load(&D[j]) - Vec8f().load(&B[j]);
Vec8f Gc = Vec8f().load(&A[j]) - Vec8f().load(&C[j]);
Vec8f invec = atan(Gs, Gc);
invec.store(&in[j]);
}
for(int j=ROUND_DOWN(H*W,8); j<H*W; j++) {
float Gs = D[j]-B[j];
float Gc = A[j]-C[j];
in[j]=atan2f(Gs,Gc);
}
}
自己进行矢量化的一个挑战是找到一个SIMD数学库(例如atan2f)。 vectorclass支持3个选项。非SIMD,AMD的LIBM和Intel的SVML(我在上面的代码中使用了非SIMD选项)。 SIMD math libraries for SSE and AVX
您可能想要考虑的最后一些评论。 Visual Studio具有自动并行化(默认情况下为off)以及自动向量化(默认情况下为on,至少在发布模式下)。您可以尝试使用此而不是OpenMP来减少代码膨胀。 http://msdn.microsoft.com/en-us/library/hh872235.aspx
此外,Microsoft还有并行模式库。由于微软的OpenMP支持有限,因此值得研究。它几乎和OpenMP一样容易使用。这些选项中的一个可能更适合自动矢量化(尽管我对此表示怀疑)。就像我说的那样,我会用vectorclass手动进行矢量化。
答案 1 :(得分:1)
您可以尝试循环展开而不是sections
:
#pragma omp parallel for
for (int j = 0; j < H*W; j += outer_stride)//A,B,C,D,IN are __restricted
{
for (int ii = 0; ii < outer_stride; ii++) {
float Gs = D[j+ii]-B[j+ii];
float Gc = A[j+ii]-C[j+ii];
in[j+ii] = atan2f(Gs,Gc);
}
}
其中outer_stride
是SIMD行的合适倍数。此外,您可能会发现此answer有用。