【问题标题】:Loop Optimization in CC 中的循环优化
【发布时间】:2014-07-30 18:02:56
【问题描述】:

我的任务是优化 C 中的特定 for 循环。这是循环:

#define ARRAY_SIZE 10000
#define N_TIMES    600000

for (i = 0; i < N_TIMES; i++)
{
    int j;

    for (j = 0; j < ARRAY_SIZE; j++)
    {
        sum += array[j];
    }
}

我应该使用循环展开、循环拆分和指针来加快速度,但每次我尝试实现某些东西时,程序都不会返回。到目前为止,这是我尝试过的:

for (i = 0; i < N_TIMES; i++) 
{
    int j,k;

    for (j = 0; j < ARRAY_SIZE; j++) 
    {    
        for (k = 0; k < 100; k += 2) 
        {
            sum += array[k];
            sum += array[k + 1];
        }
    } 
}

我不明白为什么程序现在甚至没有返回。任何帮助将不胜感激。

【问题讨论】:

  • 使用调试器。我会让编译器做优化。我想这是一些家庭作业!第二个程序与第一个程序不同,在第二种情况下,您将 array 加起来最多为 101。
  • 你的新程序的运行时间大约是原来的 100 倍。
  • @Sky 我会说大约是 sum += 两次的 100 倍......
  • 不是你问的,但你可以完全消除外循环。并且在内部循环中只有 sum += array[j] * N_TIMES;现在您可以通过 sum += *array++ * N_TIMES; 使用指针算法来获得更高的性能;
  • TBH 最好的答案可能是采用现代编译器,将其置于最大优化级别,然后查看它生成的程序集

标签: c loops optimization


【解决方案1】:

第二段代码效率低下错误,因为它比原始代码增加了更多的值。

循环展开(或在这种情况下减少,因为您可能不想展开一万次迭代循环)将是:

// Ensure ARRAY_SIZE is a multiple of two before trying this.
for (int i = 0; i < N_TIMES; i++)
    for (int j = 0; j < ARRAY_SIZE; j += 2)
        sum += array[j] + array[j+1];

但是,老实说,愚蠢的编译器时代早已一去不复返了。您通常应该将这一级别的微优化留给您的编译器,而您则专注于更高级的东西,例如数据结构、算法和人工分析。

最后一个相当重要。由于您将相同的数组添加到累积和的次数为常数,因此您只需要数组的总和一次, 然后您可以根据需要多次添加该部分总和:

int temp = 0;
for (int i = 0; i < ARRAY_SIZE; i++)
    temp += array[i];
sum += temp * N_TIMES;

它仍然是O(n),但在n 上的乘数要低得多(一个而不是六十万)。 可能 gcc 的疯狂优化级别-O3 可以解决这个问题,但我对此表示怀疑。人脑在很多领域仍然可以胜过计算机。

现在,无论如何:-)

【讨论】:

  • 谢谢。我知道这些天编译器将完成大部分工作,但任务要求我使用这些优化技术来加快速度。感谢您的帮助!
  • @user3698112:然后提交类似于第二个代码段的内容。你会把其他提交的东西从水中吹走,可能还会把你的教育者的帽子吹掉:-)
  • @paxdiablo 第二个 for 循环是一个简单的乘法 sum = temp * N_TIMES;
  • @Manül:是的,我真傻。已修改以纳入您的建议。
  • 如果您要展开,通过将两个单独的累加器相加来修复“循环携带依赖”,然后将它们添加到末尾:sum1 += array[j]; sum2 += array[j+1]; 否则展开并没有真正完成太多。
【解决方案2】:

您的程序没有任何问题...它会返回。它只需要比第一个多 50 倍...

第一次你有 2 个 fors:600.000 * 10.000 = 6.000.000.000 次迭代。

第二个你有 3 个 fors:600.000 * 10.000 * 50 = 300.000.000.000 次迭代...

【讨论】:

  • 循环次数是原来的 50 倍,但每个循环的工作量也是两倍。所以时间大概会是一百倍左右。
  • 是的,这就是为什么我使用了迭代这个词而不是操作......通过迭代我的意思是只控制循环的变量的比较/增量的数量......
  • 啊,我明白了,我只看了“多拿 50 倍”,并认为是时候了。我很抱歉。
【解决方案3】:

循环展开不会加快循环速度,而是减慢循环速度。在过去,它通过减少条件评估的数量给你一个减速带。在现代,它通过杀死缓存来减慢你的速度。

这里没有明显的循环拆分用例。要拆分循环,您需要在迭代中寻找两个或更多明显的分组。在一段时间内,您可以将array[j] 乘以i,而不是执行外部循环并声称您已将内部与外部分开,然后将外部视为无用而丢弃。

C 数组索引语法只是定义为指针算术(一种特殊的语法)。但我想你会想要这样的东西:

sum += *arrayPointer++;

代替您使用j,并适当地初始化事物。但我怀疑你会从中得到什么。

根据 cmets,如果这是现实生活,那么您只需让编译器弄清楚这些东西。

【讨论】:

  • 您可以部分地展开循环,并且仍然保持在代码缓存范围内。考虑到缓存的速度,不确定效果是否值得。但你是对的,在一个缓存代码的系统中,展开一个 10000 次迭代的循环很可能会让事情变得更糟。
  • @paxdiabli 可能是真的,但您的扩展循环更有可能将其他内容推出缓存,尤其是当它从页面边界附近开始时。但另一方面,您的分支预测器很可能正在运行下一次迭代同时它评估条件使您没有任何好处。所有这些都回避了这样一个问题,即这些都是高度依赖于架构的权衡,最好留给特定于架构的编译器。
猜你喜欢
  • 2017-02-28
  • 2011-07-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-30
  • 2018-12-23
  • 1970-01-01
  • 1970-01-01
  • 2014-09-28
相关资源
最近更新 更多