关于性能:根据C中的操作数,浮动乘法执行速度较慢

Floating multiplication performing slower depending of operands in C

我正在对以前从文件中读取的矩阵执行模板计算。我使用两种不同的矩阵(非零类型和零类型)。这两种类型都共享边界值(通常为1000),而其余元素为0(零类型)和1(非零类型)。

代码将文件的矩阵存储在两个相同大小的分配矩阵中。然后,它使用自己的值和邻居的值(加上x 4和mul x 1)在一个矩阵的每个元素中执行操作,并将结果存储在第二个矩阵中。计算完成后,交换矩阵的指针,并在有限的时间内执行相同的操作。下面是核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define GET(I,J) rMat[(I)*cols + (J)]
#define PUT(I,J) wMat[(I)*cols + (J)]

for (cur_time=0; cur_time<timeSteps; cur_time++) {
    for (i=1; i<rows-1; i++) {
        for (j=1; j<cols-1; j++) {
            PUT(i,j) = 0.2f*(GET(i-1,j) + GET(i,j-1) + GET(i,j) + GET(i,j+1) + GET(i+1,j));
        }
    }
    // Change pointers for next iteration
    auxP = wMat;
    wMat = rMat;
    rMat = auxP;
}

我要介绍的这个例子使用了固定数量的500个时间步(外部迭代),矩阵大小为8192行和8192列,但在更改时间步数或矩阵大小时,问题仍然存在。注意,我只测量算法的这个具体部分的时间,所以从文件中读取矩阵或其他任何东西都不会影响时间测量。

会发生的是,根据我使用的矩阵类型,我得到不同的时间,在使用零类型时获得更差的性能(其他每个矩阵都与非零类型执行相同,因为我已经尝试生成一个充满随机值的矩阵)。

我确信这是乘法运算,就好像我把它去掉,只留下加法,它们执行相同的运算。注意,对于零矩阵类型,大多数类型的求和结果将为0,因此操作将为"0.2*0"。

这种行为对我来说确实很奇怪,因为我认为浮点操作独立于操作数的值,这里的情况与此不同。我还尝试捕获并显示SIGFPE异常,以防出现问题,但没有得到任何结果。

如果有帮助的话,我使用的是IntelNehalem处理器和GCC4.4.3。


这个问题基本上已经被诊断出来了,但我会把这里发生的事情写下来。

本质上,发问者是在模拟扩散;边界上的初始量扩散到一个大网格的整体中。在每个时间步骤t,扩散前缘的值将为0.2^t(忽略拐角处的影响)。

最小归一化单精度值为2^-126;当cur_time = 55时,扩散前沿值为0.2^55,略小于2^-127。从现在开始,网格中的一些单元格将包含非规范值。在发问者的Nehalem上,非规范化数据的操作比标准化浮点数据的操作慢大约100倍,这解释了速度的减慢。

当网格初始填充了1.0的常量数据时,数据永远不会变得太小,因此避免了非正规失速。

请注意,将数据类型更改为double将延迟,但不会缓解此问题。如果使用双精度进行计算,则非正规值(现在小于2^-1022)将首先出现在441次迭代中。

以扩散前沿的精度为代价,您可以通过启用"flush to zero"来解决速度减慢的问题,这将导致处理器在算术运算中产生零而不是非非正规结果。这是通过在fpscr或mxscr中切换一位来完成的,最好是通过C库中头中定义的函数。

另一个(hackier,不太好)"fix"是用非常小的非零值(0x1.0p-126f,最小的正常数)填充矩阵。这也可以防止计算中出现非规范化。


也许您的ZeroMatrix使用了稀疏矩阵的典型存储方案:将每个非零值存储在一个链接列表中。如果是这种情况,那么可以理解为什么它的性能比典型的基于阵列的存储方案差:因为它需要对您执行的每个操作运行一次链接列表。在这种情况下,您可以通过使用一个矩阵乘法算法来加速这个过程,该算法考虑到有一个稀疏的矩阵。如果不是这样的话,请发布最少但完整的代码,这样我们就可以玩了。

以下是有效地乘法稀疏矩阵的一种可能性:

http://www.cs.cmu.edu/~scandal/cacm/node9.html