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

C 计算矩阵每个元素指数的最有效方法,c,performance,matrix,exponential,C,Performance,Matrix,Exponential,我正在从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解决方案的结果 所有结果都来自1024x1024for(for)循环,在该循环中,在每次迭代中分配两个doub

我正在从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}-存储矩阵的主模式时:
    • 以行主模式在内环中的行上循环时为226.56 ms(情况1)
    • 223.22毫秒,在行主模式(情况2)下在内环中的列上循环
    • 当使用gsl提供的
      gsl\u matrix\u complex\u 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);
    }
}
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);
    }
}
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);
    }
}

for(i=0;i不,除非有我没听说过的奇怪的数学怪癖,否则你几乎只需要用两个for循环遍历元素。

如果你只想对一个数字数组应用
exp
,实际上没有捷径。你必须调用它(Nx*Ny)如果一些矩阵元素很简单,比如0,或者有重复的元素,一些记忆可能会有所帮助


但是,如果您真正想要的是矩阵指数(这非常有用),那么我们所依赖的算法是。它是Fortran语言,但您可以使用它将其转换为C。

没有办法避免迭代所有元素并对每个元素调用
exp()
或等效的方法。但是迭代的速度更快,速度也更慢

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

处理缓存后,下一个目标是消除循环开销。第一步(如果您的矩阵API支持)是从嵌套循环(M&N边界)转到遍历底层数据的单个循环(MN边界)。您需要获得指向底层内存块的原始指针(即,双精度而不是双精度**)这样做


最后,加入一些循环展开(即,对循环的每个迭代执行8或16个元素),以进一步减少循环开销,这可能是尽可能快的。您可能需要一个带有fall-through的最终switch语句来清理其余元素(当数组大小%block size!=0时)由于循环的内容尚未显示,因此计算c_值的位我们不知道代码的性能是受内存带宽限制还是受CPU限制。唯一确定的方法是使用探查器,并且是一个复杂的探查器。它需要能够测量内存延迟,即CPU一直处于空闲状态,等待来自RAM的数据

如果您受到内存带宽的限制,那么在按顺序访问内存后,您就没有什么可以做的了。当按顺序提取数据时,CPU和内存工作得最好。随机访问会影响吞吐量,因为数据很可能必须从RAM中提取到缓存中。您始终可以尝试获取更快的RAM


如果您受到CPU的限制,那么还有一些选项可供选择。使用SIMD是一个选项,手工编写浮点代码也是一个选项(C/C++编译器由于许多原因不擅长FPU代码)。如果这是我,并且内部循环中的代码允许,我将有两个指针进入数组,一个在开始处,另一个在数组的四分之五处。每次迭代,将使用第一个指针执行SIMD操作,使用第二个指针执行标量FPU操作,以便循环的每次迭代都执行五个值。然后,我会将SIMD指令与FPU指令交错以降低延迟成本。这不会影响您的缓存,因为(至少在奔腾上)MMU可以同时传输多达四个数据流(即,在没有任何提示或特殊指令的情况下为您预取数据)亚历杭德罗:如果你想要
N_x*N_y
独立计算的指数,那么
A
不再是一个矩阵,而是一堆数字。:-)如果你想要一个数学专家,我会去数学论坛或类似的地方(如www.askdrmath.com)并尝试他们的知识。尝试不会有什么坏处!kaize.se:你说得对,闻起来不像是在改进双for循环:-(顺便说一句,我按照雷夫的建议把这个问题发给了数学博士,以防某位神奇的数学家在线。我想你只是认为它效率低下/愚蠢,因为你习惯了MATLAB,在那里使用嵌套fors是一种灾难。但MATLAB会在幕后完成这项工作,只是如果你把它写出来,它很糟糕。但C不会这样做,你总是写出来,它的速度和MATLAB一样快…嗯…这些性能数字是无法区分的。我很惊讶——我们能看到你在每种情况下运行的代码吗?我想知道你的编译器是否注意到这些值是独立的,并且会自动重新排序和展开循环以最大限度地提高性能。查看生成的asse