Chapel

时间:2018-01-13 19:51:10

标签: chapel

我有一段C代码,如下所示:

for(int i = 0; i < numRows; i++) {
   double *myRow = matrixPtr + (i * numCols);
   for (int j = 0; j < numCols; j++) {
      someOperation(myRow[j]);
   }
}

其中matrixPtr是存储在行主要布局中的2D矩阵。获取每行的引用/指针是为了使代码更具可读性,并避免为最内层循环中的每次访问计算行偏移量(即matrixPtr[(i*numCols)+j])。

在Chapel中,如果我要翻译上面的代码,尝试将其紧密匹配,我可以这样:

for i in 0..numRows-1 {
   ref myRow = matrix[i,..];
   for j in 0..numCols-1 {
      someOperation(myRow[j]);
   }
}

其中matrix是教堂矩阵,myRow是对矩阵行切片的引用。

我注意到,与省略数组切片步骤以获取行引用并直接通过matrix访问[i,j]相比,上述Chapel代码的性能非常慢,我认为这样做在编译器优化之后非常类似于上面的C代码(事实上,它的性能几乎与上面的C代码相同):

for i in 0..numRows-1 {
   for j in 0..numCols-1 {
      someOperation(matrix[i,j]);
   }
}

我可以理解为什么它会变慢,因为你需要执行数组切片来获取每一行。但我想知道的是为什么Chapel中的数组切片要慢得多?为了做到最好,我的教堂经验充其量只是最小的。

我试图查看生成的C代码,以便更好地了解正在发生的事情。我首先想到每次创建行引用时都会产生很多与构建有界范围相关的开销。为了测试它,我预先创建了范围并使用了它(即ref myRow = matrix[i,colRange])。但是,这似乎没有改善运行时。生成的C代码中唯一可以隔离作为潜在线索的其他部分是一个改变数组等级的函数(或沿着这些行的某些东西)。

对于透视图,这种类型的矩阵运算在我所拥有的应用程序中执行多次,其中numRows非常大并且numCols相比之下非常小,并且Chapel代码的性能在使用数组切片比使用[i,j](在编译期间使用--fast标志)直接访问矩阵 30-40x更慢

由于

更新:

这是两个小程序,应该产生报告的减速(介于20和25x之间)并使用--fast标志进行编译。 direct.chpl程序产生与获取cPtr到矩阵的版本相同的执行时间,然后计算行偏移量,如上面的帖子中所述。

slices.chpl

use Time;
var numRows = 100000;
var numCols = 35;
var D : domain(2) = {0..numRows-1, 0..numCols-1};
var mat : [D] real;
mat = 1.0;
var totalTimer : Timer;
var accum : [0..numCols-1] real;
accum = 0.0;

totalTimer.start();
for i in 0..numRows-1 {
    ref myRow = mat(i,..);
    for j in 0..numCols-1 {
        accum[j] += i * myRow[j];
    }
}

totalTimer.stop();
writeln("Accum:");
writeln(accum);
writeln("\nTotal Elapsed time: ", totalTimer.elapsed(), " seconds");

direct.chpl:

use Time;
var numRows = 100000;
var numCols = 35;
var D : domain(2) = {0..numRows-1, 0..numCols-1};
var mat : [D] real;
mat = 1.0;
var totalTimer : Timer;
var accum : [0..numCols-1] real;
accum = 0.0;

totalTimer.start();
for i in 0..numRows-1 {
    for j in 0..numCols-1 {
        accum[j] += i * mat[i,j];
    }
}

totalTimer.stop();
writeln("Accum:");
writeln(accum);
writeln("\nTotal Elapsed time: ", totalTimer.elapsed(), " seconds");

slices.chpl的输出:

Accum:
4.99995e+09 4.99995e+09 4.99995e+09 4.99995e+09...

Total Elapsed time: 0.124494 seconds

direct.chpl的输出:

Accum:
4.99995e+09 4.99995e+09 4.99995e+09 4.99995e+09...

Total Elapsed time: 0.005211 seconds

所以这似乎是运行时间的23倍差异。这不完全是我在实际应用中看到的30-40倍的差异,但它肯定比我预期的要多。

1 个答案:

答案 0 :(得分:2)

  

我想知道的是为什么Chapel中的数组切片比我的C代码慢得多?

我认为这里的答案是Chapel的数组切片旨在支持数组支持的所有操作,包括边界检查和查询,迭代,传递给采用数组参数的例程,重建索引,后续切片等。该支持需要设置元数据来描述数组及其域(索引集) - 在这种情况下,通过将{i, ..}域与{{1}}相交来计算。因此,正如您正确预期的那样,它本质上比C代码中的指针数学更多的工作。

那就是说,你的30-40倍慢的报告让我们感到惊讶。一位同事和我试图重现你的实验并看到更多在2x棒球场的开销(我的结果是在Mac笔记本电脑上收集的)。这让我们想知道你的情况会有什么不同。我最好的猜测是,关于你的程序的一些东西是跨区域边界到达远程内存,例如通过切片远程或分布式数组。在任何情况下,如果您能够共享重现此问题的代码,我建议针对此问题打开GitHub issue,并提供有关如何收集数据的说明,以支持进一步探索数据。< / p>

一般来说,我们倾向于不鼓励在性能关键代码中使用数组视图(数组切片和重建索引),特别是不能尝试重现标量C风格的优化。我们的偏好是让用户以更干净/更直接的方式编写代码,并依靠Chapel编译器对其进行优化(和/或当您发现我们在场上留下性能的情况时让我们知道)。 Chapel的数组视图主要设计为生产力功能,例如,支持创建数组或子数组的视图,以满足现有例程的形式参数要求。