【问题标题】:Improve C function performance with cache locality?使用缓存局部性提高 C 函数性能?
【发布时间】:2011-10-12 02:43:58
【问题描述】:

我必须在表示为二维数组的矩阵中找到对角线差,函数原型是

int diagonal_diff(int x[512][512])

我必须使用二维数组,数据为 512x512。这是在 SPARC 机器上测试的:我当前的时间是 6 毫秒,但我需要低于 2 毫秒。

样本数据:

[3][4][5][9]
[2][8][9][4]
[6][9][7][3]
[5][8][8][2]

区别在于:

|4-2| + |5-6| + |9-5| + |9-9| + |4-8| + |3-8| = 2 + 1 + 4 + 0 + 4 + 5 = 16

为了做到这一点,我使用以下算法:

int i,j,result=0;
for(i=0; i<4; i++)
    for(j=0; j<4; j++)
        result+=abs(array[i][j]-[j][i]);

return result;

但是这种算法一直在访问列、行、列、行等,这使得缓存的使用效率很低。

有没有办法改善我的功能?

【问题讨论】:

  • 您对此进行了基准测试或分析吗?真实矩阵有多大?任何 4 x 4 矩阵都可以放入缓存中,并且与您访问项目的顺序无关。
  • 即使每秒执行 50,000,000 次,即使是低端的现代 CPU 也不会出汗。即使是对 abs() 的函数调用也会被大多数编译器(包括 GCC 和 VC++)优化为固有的。
  • 数组的大小是 512x512,我必须使用 2D 数组。接口规范是固定的,我只需要填写implementations.int对角差异(int x[512][512], int y[512][512])
  • 该功能在 SPARC 上测试,我当前的时间是 6ms,我需要在 2ms 以下
  • 是积极的编译器优化——例如“-O6”——正在使用?

标签: c optimization matrix


【解决方案1】:

编辑:为什么面向块的方法更快?我们正在利用 CPU 的数据缓存,确保无论是逐行还是逐列迭代一个块,我们都保证整个块都适合缓存。

例如,如果您有一个 32 字节的缓存行,而 int 是 4 个字节,您可以将一个 8x8 int 矩阵放入 8 个缓存行。假设您有足够大的数据缓存,您可以按行或按列迭代该矩阵,并保证您不会破坏缓存。另一种思考方式是,如果您的矩阵适合缓存,您可以随意遍历它。

如果您有一个更大的矩阵,比如 512x512,那么您需要调整矩阵遍历,以免破坏缓存。例如,如果您以与矩阵布局相反的顺序遍历矩阵,您几乎总是会错过您访问的每个元素的缓存。

面向块的方法可确保在 CPU 必须刷新该缓存行之前,您最终将访问的数据只有一次缓存未命中。换句话说,针对缓存行大小调整的面向块的方法将确保您不会破坏缓存。

因此,如果您尝试针对正在运行的机器的缓存行大小进行优化,您可以以块的形式迭代矩阵并确保您只访问每个矩阵元素一次:

int sum_diagonal_difference(int array[512][512], int block_size)
{
    int i,j, block_i, block_j,result=0;

     // sum diagonal blocks
    for (block_i= 0; block_i<512; block_i+= block_size)
        for (block_j= block_i + block_size; block_j<512; block_j+= block_size)
            for(i=0; i<block_size; i++)
                for(j=0; j<block_size; j++)
                    result+=abs(array[block_i + i][block_j + j]-array[block_j + j][block_i + i]);

    result+= result;

     // sum diagonal
    for (int block_offset= 0; block_offset<512; block_offset+= block_size)
    {
        for (i= 0; i<block_size; ++i)
        {
            for (j= i+1; j<block_size; ++j)
            {
                int value= abs(array[block_offset + i][block_offset + j]-array[block_offset + j][block_offset + i]);
                result+= value + value;
            }
        }
    }

    return result;
}

您应该尝试使用block_size 的各种值。在我的机器上,8 与 1 的 block_size 相比导致最大的加速(2.5 倍)(与整个矩阵的原始迭代相比 ~5 倍)。 block_size 最好是 cache_line_size_in_bytes/sizeof(int)

【讨论】:

  • 在我的特定机器上,这比使用blocksize = 8 的非缓存感知(Blastfurnace)版本快 50%,这是我能得到的最快速度。也可以在不到半毫秒的时间内执行。
  • 这个方法有效!多谢 !结果错误很少,原因是:result+=result;在第 12 行,结果+= 值+值;在第 22 行。我将其更改为使用单结果而不是双结果(@MSN 就是这样做的),并且效果很好。
  • 在我的测试中,block_size = 16 是我能得到的最快的。该方法比原始方法快约 80%。
  • @ChristoperHans,您是说有溢出(是)还是结果不正确?我使用您发布的幼稚版本在本地对此进行了测试,结果它们具有相同的值。
  • 在数学上,MSN 的解决方案通过对每对计数两次来产生正确的结果。 Blastfurnace 的解决方案实际上产生的结果恰好是原始解决方案应输出的一半。
【解决方案2】:

如果你有像 intel MKL 这样好的向量/矩阵库,也可以试试向量化的方式。

在matlab中非常简单: 结果 = sum(sum(abs(x-x')));

我在matlab中也复现了Hans的方法和MSN的方法,结果是:

Elapsed time is 0.211480 seconds.  (Hans)

Elapsed time is 0.009172 seconds.  (MSN)

Elapsed time is 0.002193 seconds.  (Mine)

【讨论】:

    【解决方案3】:

    只需稍作改动,您就可以让循环仅在所需的索引上运行。我刚刚更改了j 循环初始化。

    int i, j, result = 0;
    for (i = 0; i < 4; ++i) {
        for (j = i + 1; j < 4; ++j) {
            result += abs(array[i][j] - array[j][i]);
        }
    }
    

    【讨论】:

    • 我这样做了,但这并没有太大改善。我还尝试在比较之前将该列复制到另一个一维数组。时间约 4 毫秒
    • 这里不需要i != j。因为你初始化了j = i + 1,所以j永远不可能等于i。
    • @Mike Bantegui,你是对的,我觉得有点愚蠢。谢谢。
    猜你喜欢
    • 2010-12-27
    • 1970-01-01
    • 1970-01-01
    • 2015-08-02
    • 2018-09-04
    • 1970-01-01
    • 2011-03-17
    • 1970-01-01
    • 2012-08-13
    相关资源
    最近更新 更多