【问题标题】:for loop optimization c ++for循环优化c ++
【发布时间】:2013-06-11 02:51:02
【问题描述】:

这是我第一次在这个网站上发帖,希望能得到一些帮助/提示。我有一个任务,我需要优化内部 for 循环的性能,但我不知道该怎么做。代码在作业中给出。我需要计算时间(我能够做到)并提高性能。

代码如下:

//header files

#define N_TIMES     200   //This is originally 200000 but changed it to test the          program faster    
#define ARRAY_SIZE    9973

int main (void) {
  int  *array = (int*)calloc(ARRAY_SIZE, sizeof(int));
  int  sum = 0;
  int  checksum = 0;
  int  i;
  int  j;
  int  x; 

  // Initialize the array with random values 0 to 13. 
  srand(time(NULL));
  for (j=0; j < ARRAY_SIZE; j++) {    
    x = rand() / (int)(((unsigned)RAND_MAX + 1) / 14);
    array[j] = x;
    checksum += x;
  }
  //printf("Checksum is %d.\n",checksum);

  for (i = 0; i < N_TIMES; i++) {
    // Do not alter anything above this line.
    // Need to optimize this for loop----------------------------------------
    for (j=0; j < ARRAY_SIZE; j++) {
      sum += array[j];
      printf("Sum is now: %d\n",sum);
    }


    // Do not alter anything below this line.
    // ---------------------------------------------------------------

    // Check each iteration.  
    //
    if (sum != checksum) {
      printf("Checksum error!\n");
    }
    sum = 0;

  } 
  return 0;
}

代码运行大约需要 695 秒。请问如何优化它的任何帮助? 非常感谢。

【问题讨论】:

  • 请格式化您的代码。
  • 将打印移出循环 - 这是迄今为止该循环中成本最高的操作。
  • 没什么可优化的,去掉printf语句需要多长时间?
  • 告诉编译器对其进行优化。对于 gcc,这意味着 g++ -O3 [other stuff]。说真的,直到你探索了编译器的能力,才考虑将如何优化是愚蠢到近乎愚蠢的程度。

标签: c++ optimization


【解决方案1】:

那个循环的瓶颈显然是printf做的IO;由于您可能正在控制台上编写输出,因此输出是行缓冲的,这意味着每次迭代都会刷新 stdio 缓冲区,这会大大减慢速度。

如果您必须完成所有打印,则可以通过强制流进行块缓冲来大大提高性能:在 for 之前添加一个

setvbuf(stdout, NULL, _IOFBF, 0);

或者,如果这种方法被认为无效,您可以通过自己分配一个大缓冲区并自己进行缓冲来进行自己的缓冲:使用sprintf 写入缓冲区,定期在输出流中清空它带有fwrite

此外,您可以使用穷人的缓冲方法 - 只需使用一个足够大的缓冲区来写入所有内容(您可以很容易地计算出它必须有多大)并在其中写入而不用担心它何时已满,何时清空它,... - 只需在循环结束时清空它。 编辑:请参阅@paxdiablo 的答案以获取此示例


仅应用第一个优化,time 得到的是

real    0m6.580s
user    0m0.236s
sys     0m2.400s

对比原版

real    0m8.451s
user    0m0.700s
sys     0m3.156s

因此,我们的实时时间缩短了约 3 秒,用户时间缩短了半秒,系统时间缩短了约 0.7 秒。但是这里我们看到的是user+sys和real的巨大区别,也就是说时间不是花在进程内部做某事,而是等待。

因此,这里真正的瓶颈不是在我们的进程中,而是在虚拟终端模拟器的进程中:向控制台发送大量文本无论如何都会很慢我们在程序中做了哪些优化;换句话说,你的任务不是 CPU 密集型的,而是 IO 密集型的,所以以 CPU 为目标的优化不会有太大的好处,因为最终你必须等待你的 IO 设备做他的慢动作。

真正加速这样一个程序的方法会简单得多:避免使用慢速 IO 设备(控制台),只需将数据写入文件(顺便说一下,默认情况下是块缓冲的)。

matteo@teokubuntu:~/cpp/test$ time ./a.out > test

real    0m0.369s
user    0m0.240s
sys     0m0.068s

【讨论】:

    【解决方案2】:

    由于基于i(外循环)的循环绝对没有变化,因此您无需每次都计算。

    此外,数据的打印应该内部循环之外,以免对计算产生I/O成本。

    考虑到这两点,一种可能性是:

    static int sumCalculated = 0;
    if (!sumCalculated) {
        for (j=0; j < ARRAY_SIZE; j++) {
            sum += array[j];
        }
        sumCalculated = 1;
    }
    printf("Sum is now: %d\n",sum);
    

    虽然它的输出与原始输出不同,这可能是一个问题(最后一行而不是每次添加一行)。

    如果您确实需要在循环中打印累计和,我也只需将其缓冲(因为它不会每次通过i 循环而变化。

    字符串Sum is now: 999999999999\n(12 位,可能因您的int 大小而异)占用25 个字节(不包括终止NUL)。将其乘以 9973,您需要大约 250K 的缓冲区(包括终止 NUL)。所以是这样的:

    static char buff[250000];
    static int sumCalculated = 0;
    
    if (!sumCalculated) {
        int offset = 0;
        for (j=0; j < ARRAY_SIZE; j++) {
            sum += array[j];
            offset += sprintf (buff[offset], "Sum is now: %d\n",sum);
        }
        sumCalculated = 1;
    }
    printf ("%s", buff);
    

    现在这有点违背了作为基准工具的外循环的全部意图,但循环不变的删除是一种有效的优化方法。

    【讨论】:

    • 嗯,这行不通,在外循环的每次迭代中,sumCalculated 都会重新初始化;另外,我认为“sum is now”消息必须在循环内打印(优化后的程序不会给出与原始程序相同的输出)。
    • @Matteo,static 解决了这个问题。我会更新你的第二点。
    【解决方案3】:

    将 printf 移到 for 循环之外。

       // Do not alter anything above this line.
       //Need to optimize this for loop----------------------------------------
        for (j=0; j < ARRAY_SIZE; j++) {
            sum += array[j];
        }
       printf("Sum is now: %d\n",sum);
    
        // Do not alter anything below this line.
        // ---------------------------------------------------------------
    

    【讨论】:

    • 但是,很明显,这会有所不同。如果他希望它打印每一个通道,那么这是错误的。
    • 感谢大家的这些提示。将 printif 移到循环外并没有提供与以前相同的输出。另外,我不允许使用 IDE 优化期货。我试图弄清楚如何使用展开循环。我在网上阅读了一些示例,但还无法使用!
    • @samy:循环展开和类似的优化不会有帮助,因为您的任务受 IO 限制,而不是 CPU 限制。您可以使用缓冲从 IO 中去除一些“脂肪”,但真正的问题是您正在执行 IO 的设备(控制台)非常慢,并且代码中的任何优化都无法克服这个事实。
    • 嗨,Matteo,我同意你的观点,但任务的重点是比较程序在同一台机器上优化前后的性能。
    • @samy:如果你问我,我认为这个练习的重点是了解 IO 密集型任务基本上不受 CPU 目标优化的影响。
    【解决方案4】:
    1. 让 I/O 脱离循环是一个很大的帮助。
    2. 根据编译器和机器的不同,您可能可以通过使用指针而不是索引来略微提高速度(尽管在现代硬件上,这通常不会产生影响)。
    3. 循环展开可能有助于提高有用工作与循环开销的比率。
    4. 您可以使用向量指令(例如 SIMD)并行执行大量计算。
    5. 可以打包阵列吗?你能使用比 int 更小的类型的数组吗(假设所有的值都非常小)?使阵列在物理上更短可以提高局部性。

    循环展开可能如下所示:

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

    如果数组不是展开大小的精确倍数(这可能是赋值使用素数的原因),您必须弄清楚该怎么做。

    您必须进行试验,看看展开多少才是正确的。

    【讨论】:

      猜你喜欢
      • 2018-12-23
      • 2011-07-04
      • 2010-11-15
      • 2011-08-30
      • 1970-01-01
      • 2014-09-28
      • 2015-04-15
      • 1970-01-01
      • 2017-09-01
      相关资源
      最近更新 更多