我正在玩ICC中的自动并行化(11.1;旧的,但对此无能为力),我想知道为什么编译器无法并行化内部循环以进行简单的高斯消除:< / p>
void makeTriangular(float **matrix, float *vector, int n) {
for (int pivot = 0; pivot < n - 1; pivot++) {
// swap row so that the row with the largest value is
// at pivot position for numerical stability
int swapPos = findPivot(matrix, pivot, n);
std::swap(matrix[pivot], matrix[swapPos]);
std::swap(vector[pivot], vector[swapPos]);
float pivotVal = matrix[pivot][pivot];
for (int row = pivot + 1; row < n; row++) { // line 72; should be parallelized
float tmp = matrix[row][pivot] / pivotVal;
for (int col = pivot + 1; col < n; col++) { // line 74
matrix[row][col] -= matrix[pivot][col] * tmp;
}
vector[row] -= vector[pivot] * tmp;
}
}
}
我们只是写入依赖于私有行(和col)变量的数组,并且保证行大于pivot,因此编译器应该明白我们不会覆盖任何内容。
我正在使用-O3 -fno-alias -parallel -par-report3
进行编译,并且获得了很多依赖项ala:assumed FLOW dependence between matrix line 75 and matrix line 73.
或assumed ANTI dependence between matrix line 73 and matrix line 75.
,而且仅针对第75行。编译器有什么问题?显然,我可以准确地告诉它如何处理一些编译指示,但我想了解编译器可以单独使用什么。
答案 0 :(得分:2)
icc 12.1上存在同样的自动并行化问题。所以我用这个新版本进行实验。
将输出矩阵添加到函数的参数列表中,并将第三个循环的主体更改为
out[row][col] = matrix[row][col] - matrix[pivot][col] * tmp;
修复了&#34; FLOW依赖&#34;问题。这意味着,&#34; -fno-alias&#34;仅影响函数参数,而单个参数的内容仍然被怀疑是别名。我不知道为什么这个选项不会影响一切。由于矩阵的不同部分之间并没有真正的别名,您可以将此附加参数留给函数,并通过此参数传递相同的矩阵。
有趣的是,在抱怨&#39; matrix&#39;时,编译器没有提及&#39; vector&#39;,它确实存在别名问题:此行vector[row] -= vector[pivot] * tmp;
可能会导致错误的别名(写入一个线程中的vector[row]
可以触及缓存行,存储vector[pivot]
,由每个线程使用。)
&#34;流动依赖&#34;这不是这段代码中唯一的问题。在修复之后,编译器仍然拒绝并行化第二个和第三个循环,因为计算工作不充分&#34;。所以我试着给它一些额外的工作:
float tmp = matrix[row][pivot] * pivotVal;
...
out[row][col] = matrix[row][col] - matrix[pivot][col] *tmp /pivotVal /pivotVal;
毕竟,第二个循环最后是并行化的,但我不确定它是否获得了任何速度提升。
更新:我找到了更好的替代方案来提供计算机&#34;一些额外的工作&#34;。选项-par-threshold50
可以解决问题。
答案 1 :(得分:2)
基本上,编译器无法弄清楚由于名称matrix
而且没有依赖关系,名称vector
也被读取和写入(即使具有不同的区域)。您可以通过以下方式解决这个问题(尽管有点脏):
void makeTriangular(float **matrix, float *vector, int n)
{
for (int pivot = 0; pivot < n - 1; pivot++)
{
// swap row so that the row with the largest value is
// at pivot position for numerical stability
int swapPos = findPivot(matrix, pivot, n);
std::swap(matrix[pivot], matrix[swapPos]);
std::swap(vector[pivot], vector[swapPos]);
float pivotVal = matrix[pivot][pivot];
float **matrixForWriting = matrix; // COPY THE POINTER
float *vectorForWriting = vector; // COPY THE POINTER
// (then parallelize this next for loop as you were)
for (int row = pivot + 1; row < n; row++) {
float tmp = matrix[row][pivot] / pivotVal;
for (int col = pivot + 1; col < n; col++) {
// WRITE TO THE matrixForWriting VERSION
matrixForWriting[row][col] = matrix[row][col] - matrix[pivot][col] * tmp;
}
// WRITE TO THE vectorForWriting VERSION
vectorForWriting[row] = vector[row] - vector[pivot] * tmp;
}
}
}
底线只是给你正在写的一个暂时不同的名字来欺骗编译器。我知道它有点脏我不推荐这种编程。但如果你确定你没有数据依赖,那就完全没问题。
事实上,我会围绕它发表一些评论,这些评论对于未来的人来说非常清楚,他们认为这是一个解决方法,以及为什么要这样做。
编辑:我认为@FPK基本上触及了答案,@ Evgeny Kluev发布了答案。但是,在@Evgeny Kluev的回答中,他建议将其作为输入参数,并且可能并行但不会给出正确的值,因为matrix
中的条目不会更新。我认为上面发布的代码也会给出正确答案。
答案 2 :(得分:1)
我无法访问icc来测试我的想法,但我怀疑编译器担心别名:matrix定义为float **:指向浮点数组的指针数组。所有这些指针都指向同一个浮点数组,因此平行化这将是非常危险的。这没有任何意义,但编译器无法知道。