chapel - Chapel 中的数组切片性能

标签 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 是以行优先布局存储的二维矩阵。获取对每一行的引用/指针是为了使代码更具可读性,并避免需要为最内层循环中的每个访问计算行偏移量(即 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 是 Chapel 矩阵,myRow 是对矩阵的行切片的引用。

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

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

我能理解为什么它会变慢,因为您需要执行数组切片来获取每一行。但我想知道的是,为什么 Chapel 中的数组切片要慢得多?坦率地说,我在 Chapel 的经历充其量是非常少的。

我试图查看生成的 C 代码以更好地理解正在发生的事情。我最初认为每次创建行引用时构建有界范围会带来很多开销。为了对此进行测试,我预先创建了范围并使用了它(即 ref myRow = matrix[i,colRange])。但是,这似乎并没有改善运行时间。在生成的 C 代码中,我可以作为潜在线索分离出来的唯一另一部分是一个函数,它可以更改数组的等级(或类似的东西)。

从角度来看,这种类型的矩阵运算在我的一个应用程序中执行了很多次,其中 numRows 非常大而 numCols 相比之下非常小,并且使用数组切片时 Chapel 代码的性能比使用 [i,j] 直接访问矩阵(使用 --fast编译期间使用的标志)。

谢谢

更新:

这里有两个小程序,它们应该会产生所报告的减速(大约在 20 到 25 倍之间)并使用 --fast 标志进行编译。 direct.chpl 程序产生的执行时间与获取矩阵的 cPtr 然后计算行偏移量的版本相同,如上文所述。

切片.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");

直接.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 倍的差异,但肯定比我预期的要多。

最佳答案

what I'd like to know is why array slicing in Chapel is so much slower [than my C code]?

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

也就是说,您报告的慢 30-40 倍让我们感到惊讶。我和一位同事尝试重现您的实验,发现开销大约是 2 倍(我的结果是在 Mac 笔记本电脑上收集的)。这让我们想知道您的情况会有什么不同。我最好的猜测是关于您的程序的某些东西正在跨越语言环境边界到达远程内存,例如通过切片远程或分布式阵列。无论如何,如果您能够共享重现此问题的代码,我建议您打开 GitHub issue针对这个问题,提供有关如何收集数据以支持进一步探索的说明。

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

关于chapel - Chapel 中的数组切片性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48243717/

相关文章:

chapel - 可以将类型实例化与值实例化分开吗?

pass-by-reference - Chapel 什么时候通过引用传递,什么时候通过常量传递?

chapel - 从 Chapel 设置退出值

chapel - 增加通信操作的交易规模

multithreading - 如果运行 "inside"VM,我们会在 Chapel 中看到预期的加速吗?

arrays - 在 Chapel 中以另一个数组作为索引对一个数组进行排序

arrays - 有没有办法自定义 Chapel 中全数组语句的默认并行化行为?

chapel - 使用嵌套的“forall”循环是否有任何利弊?

chapel - ReplicatedDist 和 PrivateDist 的区别,何时使用哪个