这里是从程序提取问题。矩阵img[][]有大小大小 × 大小,并且在初始化︰

img[j][i] = 2 * j + i

然后,使res[][]矩阵,并在这里的每个字段进行是 9 中的字段在其周围 img 矩阵的平均值。为简单起见 0 保留边框。

for(i=1;i<SIZE-1;i++) 
    for(j=1;j<SIZE-1;j++) {
        res[j][i]=0;
        for(k=-1;k<2;k++) 
            for(l=-1;l<2;l++) 
                res[j][i] += img[j+l][i+k];
        res[j][i] /= 9;
}

这就是全部没有到该程序。完整,这是什么之前。没有代码之后。正如您所看到的它是只是初始化。

#define SIZE 8192
float img[SIZE][SIZE]; // input image
float res[SIZE][SIZE]; //result of mean filter
int i,j,k,l;
for(i=0;i<SIZE;i++) 
    for(j=0;j<SIZE;j++) 
        img[j][i] = (2*j+i)%8196;

基本上,此程序很慢,当大小倍数 2048,例如执行时间︰

SIZE = 8191: 3.44 secs
SIZE = 8192: 7.20 secs
SIZE = 8193: 3.18 secs

编译器是 GCC。从我知道的这是由于内存管理,但我真的不知道太多关于该主题,这就是我想在这里问一下原因。

如何解决此问题也是很棒,但如果有人能解释这些执行时间已很高兴足够。

我已经知道的 malloc/释放的但问题不是使用的内存量,它是只是执行时间,因此我不知道,可以如何帮助。

2012-09-04 13:51:31
问题评论:

@bokan 的大小是缓存的关键跨距的倍数时发生。

@Mysticial,它并不重要,它提供了完全相同的问题;代码可以是不同的但基本上这两个问题要问的同一个时间 (和头衔肯定类似)。

不应处理使用 2 维数组,如果需要高性能的图像。考虑在原始的所有像素和类似的一维数组进行处理。不要在两个传递此模糊。第一次添加周围像素,且采用滑动总和为 3 个像素的值︰ slideSum + = src [i + 1] 的 src [i-1];目标 [i] = slideSum;。然后垂直执行相同的操作,在相同的时间划分︰ 目标 [i] =(src[i-width]+src[i]+src[i+width])/9。www-personal.engin.umd.umich.edu/ ~jwvm/ece581/18_RankedF.pdf

没有实际正在做两个事情。它不是只是超级的对齐方式。

(在您的答案只是小 nitpick。第一个代码段,它就好了如果所有您循环有大括号的。)

回答:

差异是由相同的超级对齐问题从以下的相关问题引起的︰

但这只是因为没有一个其他代码的问题。

从原始的循环的开始︰

for(i=1;i<SIZE-1;i++) 
    for(j=1;j<SIZE-1;j++) {
        res[j][i]=0;
        for(k=-1;k<2;k++) 
            for(l=-1;l<2;l++) 
                res[j][i] += img[j+l][i+k];
        res[j][i] /= 9;
}

第一次发现两个内环都微不足道。它们可以展开,如下所示︰

for(i=1;i<SIZE-1;i++) {
    for(j=1;j<SIZE-1;j++) {
        res[j][i]=0;
        res[j][i] += img[j-1][i-1];
        res[j][i] += img[j  ][i-1];
        res[j][i] += img[j+1][i-1];
        res[j][i] += img[j-1][i  ];
        res[j][i] += img[j  ][i  ];
        res[j][i] += img[j+1][i  ];
        res[j][i] += img[j-1][i+1];
        res[j][i] += img[j  ][i+1];
        res[j][i] += img[j+1][i+1];
        res[j][i] /= 9;
    }
}

因此,留下两个外部的循环,我们感兴趣。

现在的问题是相同的这个问题,我们可以看到︰为什么不循环的顺序会影响性能时在二维数组循环?

循环矩阵按列而不是按行。


若要解决此问题,应交换两个循环。

for(j=1;j<SIZE-1;j++) {
    for(i=1;i<SIZE-1;i++) {
        res[j][i]=0;
        res[j][i] += img[j-1][i-1];
        res[j][i] += img[j  ][i-1];
        res[j][i] += img[j+1][i-1];
        res[j][i] += img[j-1][i  ];
        res[j][i] += img[j  ][i  ];
        res[j][i] += img[j+1][i  ];
        res[j][i] += img[j-1][i+1];
        res[j][i] += img[j  ][i+1];
        res[j][i] += img[j+1][i+1];
        res[j][i] /= 9;
    }
}

因此不再上大二的幂获得随机减慢速度,这完全消除无序的所有访问权限。


酷睿 i7 920 @ 3.5 g h z

原来的代码︰

8191: 1.499 seconds
8192: 2.122 seconds
8193: 1.582 seconds

互换外循环︰

8191: 0.376 seconds
8192: 0.357 seconds
8193: 0.351 seconds

我会还请注意,unrolling 内环对性能没有影响。编译器可能会自动。我打开它们目的消除他们能够更容易地发现外环的问题。

然后,您可以加快此代码由三个通过缓存的每一行沿总和的另一个因素。但是,和其他优化是超出原来问题的范围。

@ClickUpvote 这是确实的 (缓存) 的硬件问题。它与语言无关。如果您尝试在其他语言的编译或 Jit 为本机代码,您可能会看到相同的效果。

@ClickUpvote︰ 您看起来而不是被误导。"第二个循环"已不仅仅是神秘的手动 unrolling 内环。这是您的编译器将几乎肯定吗,做的事情并且神秘只未使外环的问题更加明显。它并不意味着应费神做自己的事情。

这是上等的好答案的一个完美示例︰ 引用类似的问题,解释您接近的方式逐步、 解释该问题,解释如何解决该问题,具有很好的格式,和甚至在您的计算机上运行的代码的示例。感谢您您的发布内容。

以下测试已完成用 Visual C++ 编译器默认值 (我猜出的没有优化标志) 的 Qt 创建者安装用。当使用 GCC,神秘的版本和我"优化"的代码没有大区别。所以结论也注意关闭好于人类 (最后一个 me) 微优化编译器优化。我将我的答案供参考的其余部分。


并非有效处理这种方式的图像。最好使用一维数组。处理所有像素是在完成一个循环。无法完成随机访问点使用︰

pointer + (x + y*width)*(sizeOfOnePixel)

在此特定情况下,最好以计算和水平缓存三个像素组的总和,因为每三次使用它们。

我已经完成了一些测试,我认为值得共享。每个结果是五个测试的平均值。

通过 user1615209 的原始代码︰

8193: 4392 ms
8192: 9570 ms

神秘的版本︰

8193: 2393 ms
8192: 2190 ms

两个传递一维数组︰ 第一个过程中水平的总和,第二次为垂直求和与求平均值。两个传递三个指针寻址,并只增加如下︰

imgPointer1 = &avg1[0][0];
imgPointer2 = &avg1[0][SIZE];
imgPointer3 = &avg1[0][SIZE+SIZE];

for(i=SIZE;i<totalSize-SIZE;i++){
    resPointer[i]=(*(imgPointer1++)+*(imgPointer2++)+*(imgPointer3++))/9;
}

8193: 938 ms
8192: 974 ms

两个传递使用一维数组和地址如下︰

for(i=SIZE;i<totalSize-SIZE;i++){
    resPointer[i]=(hsumPointer[i-SIZE]+hsumPointer[i]+hsumPointer[i+SIZE])/9;
}

8193: 932 ms
8192: 925 ms

一次缓存水平总和只在一行提前使其保持在高速缓存中︰

// Horizontal sums for the first two lines
for(i=1;i<SIZE*2;i++){
    hsumPointer[i]=imgPointer[i-1]+imgPointer[i]+imgPointer[i+1];
}
// Rest of the computation
for(;i<totalSize;i++){
    // Compute horizontal sum for next line
    hsumPointer[i]=imgPointer[i-1]+imgPointer[i]+imgPointer[i+1];
    // Final result
    resPointer[i-SIZE]=(hsumPointer[i-SIZE-SIZE]+hsumPointer[i-SIZE]+hsumPointer[i])/9;
}

8193: 599 ms
8192: 652 ms

结论︰

  • 使用多个指针和只是 (我认为简直快) 的增量没有好处
  • 缓存水平总和比计算它们几次要好。
  • 两次传递非但不能快了三倍,两倍。
  • 很可能将使用这两个一次和缓存中间结果快 3.6 时间缩短

我敢肯定就能做得更好。

请注意请注意,编写此答案一般目标性能问题而不是缓存问题所述神秘的极好的答案。在开始时它是只是伪代码。我需要做的注释中的测试...这里是完全重构后的测试版本。

"我认为它是快至少 3 倍"— — 注意备份与一些指标或引文这一点吗?

@AdamRosenfield"我认为"= 推测来进行 !"它是"= = 索赔。我对此有任何指标,希望看到一个测试。但我需要 7 增量、 2 子,2 添加和每个像素的一个 div。在 CPU 中注册使用较少本地 var 不是有每个循环。其他需要 7 递增、 递减 6 1 div 和根据编译器优化解决 10 至 20 mul 之间。此外在循环中的每一条指令需要上一条指令,此放弃的结果超级标量的 Pentiums 的体系结构的好处。因此,它具有更快。

原来问题的答案是所有关于内存和高速缓存的效果。OP 的代码是如此缓慢的原因是,其内存访问模式去往按列而不是按行,哪有很差的缓存位置引用。特别是不善于 8192 是因为则连续行最终使用同样的高速缓存线路直接映射缓存或高速缓存中具有较低的相关性,因此高速缓存不命中速率可能会更高。通过极大地提高了缓存局部性,交换循环提供了巨大的性能提升。

所以,尽管您可能能够通过计算说明挤压有点更高的性能和像您这样的微优化的同时,来自行才能最大限度地缓存地址 (它还完) 进行数据通过一遍大、 大的性能收益。我相信 3 倍的收益 (或更多) 循环交换中,由于原始代码但获得神秘的答案绝对不是 3 倍。

我相当担心早上因为我不能再现测试的 @AdamRosenfield。它似乎只与 Visual C++ 编译器性能提高。如果使用 gcc,则只有一个小小的变化。

请输入您的翻译

Why is my program slow when looping over exactly 8192 elements?

确认取消