三角矩阵转换和自动并行化

时间:2012-04-27 11:38:04

标签: c++ optimization compiler-optimization icc

我正在玩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行。编译器有什么问题?显然,我可以准确地告诉它如何处理一些编译指示,但我想了解编译器可以单独使用什么。

3 个答案:

答案 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 **:指向浮点数组的指针数组。所有这些指针都指向同一个浮点数组,因此平行化这将是非常危险的。这没有任何意义,但编译器无法知道。