繁体   English   中英

最有效的方法来计算矩阵的每个元素的指数

[英]Most efficient way to calculate the exponential of each element of a matrix

我正在从Matlab迁移到C + GSL,我想知道什么是计算矩阵B的最有效方法:

B[i][j] = exp(A[i][j])

其中i在[0,Ny]中,j在[0,Nx]中。

请注意,这与矩阵指数不同:

B = exp(A)

这可以通过GSL(linalg.h)中的一些不稳定/不支持的代码来完成。

我刚刚找到了强力解决方案(几个'for'循环),但有没有更明智的方法呢?

编辑

来自Drew Hall的解决方案的结果

所有结果都来自1024x1024 for(for)循环,其中在每次迭代中分配两个double值(复数)。 时间是超过100次执行的平均时间

  • 考虑{Row,Column} - 存储矩阵的主要模式时的结果:
    • 在Row-Major模式下循环内部循环中的行时为226.56 ms(情况1)。
    • 在Row-Major模式下循环内循环中的列时为223.22 ms(情况2)。
    • 使用GSL提供的gsl_matrix_complex_set函数时的224.60 ms(案例3)。

案例1的源代码

for(i=0; i<Nx; i++)
{
    for(j=0; j<Ny; j++)
    {
        /* Operations to obtain c_value (including exponentiation) */
        matrix[2*(i*s_tda + j)] = GSL_REAL(c_value);
        matrix[2*(i*s_tda + j)+1] = GSL_IMAG(c_value);
    }
}

案例2的源代码

for(i=0; i<Nx; i++)
{
    for(j=0; j<Ny; j++)
    {
        /* Operations to obtain c_value (including exponentiation) */
        matrix->data[2*(j*s_tda + i)] = GSL_REAL(c_value);
        matrix->data[2*(j*s_tda + i)+1] = GSL_IMAG(c_value);
    }
}

案例3的源代码

for(i=0; i<Nx; i++)
{
    for(j=0; j<Ny; j++)
    {
        /* Operations to obtain c_value (including exponentiation) */
        gsl_matrix_complex_set(matrix, i, j, c_value);
    }
}

没有办法避免遍历所有元素并在每个元素上调用exp()或等效元素。 但是有更快更慢的迭代方式。

特别是,您的目标应该是最大限度地减少缓存未命中。 找出你的数据是以行主要顺序还是按列主顺序存储,并确保循环使得内部循环迭代在内存中连续存储的元素,并且外部循环将大步向下一行( if row major)或column(如果是major major)。 虽然这看起来微不足道,但它可以在性能上产生巨大的差异(取决于矩阵的大小)。

处理完缓存后,您的下一个目标是消除循环开销。 第一步(如果您的矩阵API支持它)是从嵌套循环(M&N边界)到迭代基础数据(M N界限)的单个循环 你需要得到一个指向底层内存块的原始指针(即双倍而不是双倍**)才能做到这一点。

最后,抛出一些循环展开(也就是说,为循环的每次迭代做8或16个元素)以进一步减少循环开销,这可能就像你可以做到的那样快。 你可能需要一个带有fall-through的最终switch语句来清理其余的元素(当你的数组大小为%block size!= 0时)。

不,除非有一些我没有听说过的奇怪的数学怪癖,你几乎只需要用两个for循环遍历元素。

如果您只想将exp应用于数组,那么实际上没有捷径。 你得打电话给它(Nx * Ny)次。 如果某些矩阵元素很简单,比如0,或者有重复的元素,那么一些memoization可能会有所帮助。

但是,如果您真正想要的是矩阵指数(这是非常有用的),我们依赖的算法是DGPADM 它在Fortran中,但您可以使用f2c将其转换为C. 这是关于它的文章。

由于未显示循环的内容,计算c_value的位我们不知道代码的性能是受内存带宽限制还是受CPU限制。 确切知道的唯一方法是使用分析器,并使用复杂的分析器。 它需要能够测量内存延迟,即CPU等待数据从RAM到达的空闲时间。

如果您受内存带宽的限制,那么一旦您按顺序访问内存,就无法做很多事情。 顺序获取数据时,CPU和内存最有效。 随机访问达到了吞吐量,因为数据更有可能必须从RAM中提取到缓存中。 你总是可以尝试获得更快的RAM。

如果您受到CPU的限制,那么您可以使用更多选项。 使用SIMD是一种选择,手动编码浮点代码(由于许多原因,C / C ++编译器在FPU代码上不是很好)。 如果这是我,并且内循环中的代码允许它,我将有两个指向数组的指针,一个在开始时,另一个在第4个/ 5个中。 在每次迭代中,将使用第一指针和使用第二指针的标量FPU操作来执行SIMD操作,使得循环的每次迭代执行五个值。 然后,我将SIMD指令与FPU指令交错,以降低延迟成本。 这不应该影响您的缓存,因为(至少在Pentium上)MMU可以同时流式传输多达四个数据流(即为您预取数据而无需任何提示或特殊指令)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM