循环拆分使代码变慢

时间:2016-05-30 23:14:42

标签: c performance optimization

所以我优化了一个循环(作为家庭作业),增加了10,000个元素600,000次。没有优化的时间是23.34s~我的目标是B小于7秒,A小于5。

所以我首先按照这样展开循环开始我的优化。

int     j;

        for (j = 0; j < ARRAY_SIZE; j += 8) {
            sum += array[j] + array[j+1] + array[j+2] + array[j+3] + array[j+4] + array[j+5] +  array[j+6] + array[j+7];

这会将运行时间减少到大约6.4秒(如果我进一步展开,我可以达到6左右)。

所以我想我会尝试添加子总和并在最后总结一下以节省读写依赖的时间,我想出了这样的代码。

int     j;

    for (j = 0; j < ARRAY_SIZE; j += 8) {
        sum0 += array[j] + array[j+1]; 
        sum1 += array[j+2] + array[j+3];
        sum2 += array[j+4] + array[j+5]; 
        sum3 += array[j+6] + array[j+7];

然而,将运行时间增加约为6.8秒

我尝试使用指针的类似技术,我能做的最好的事情是大约15秒。

我只知道我运行它的机器(因为它是学校购买的服务)是一台32位,远程,基于Intel的Linux虚拟服务器,我认为它运行的是Red Hat。 / p>

我已经尝试了所有我能想到的加速代码的技术,但它们似乎都有相反的效果。有人可以详细说明我做错了什么吗?还是我可以用来降低运行时间的另一种技术?老师能做的最好的事情是大约4.8秒。

作为附加条件,我在完成的项目中不能有超过50行代码,因此可能无法做复杂的事情。

以下是两个来源的完整副本

    #include <stdio.h>
#include <stdlib.h>

// You are only allowed to make changes to this code as specified by the comments in it.

// The code you submit must have these two values.
#define N_TIMES     600000
#define ARRAY_SIZE   10000

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

    // You can add variables between this comment ...

//  double sum0 = 0;
//  double sum1 = 0;
//  double sum2 = 0;
//  double sum3 = 0;

    // ... and this one.

    // Please change 'your name' to your actual name.
    printf("CS201 - Asgmt 4 - ACTUAL NAME\n");

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

        // You can change anything between this comment ...

        int     j;

        for (j = 0; j < ARRAY_SIZE; j += 8) {
            sum += array[j] + array[j+1] + array[j+2] + array[j+3] + array[j+4] + array[j+5] +  array[j+6] + array[j+7];
        }

        // ... and this one. But your inner loop must do the same
        // number of additions as this one does.

        }

    // You can add some final code between this comment ...
//  sum = sum0 + sum1 + sum2 + sum3;
    // ... and this one.

    return 0;
}

分解代码

    #include <stdio.h>
#include <stdlib.h>

// You are only allowed to make changes to this code as specified by the comments in it.

// The code you submit must have these two values.
#define N_TIMES     600000
#define ARRAY_SIZE   10000

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

    // You can add variables between this comment ...

    double sum0 = 0;
    double sum1 = 0;
    double sum2 = 0;
    double sum3 = 0;

    // ... and this one.

    // Please change 'your name' to your actual name.
    printf("CS201 - Asgmt 4 - ACTUAL NAME\n");

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

        // You can change anything between this comment ...

        int     j;

        for (j = 0; j < ARRAY_SIZE; j += 8) {
            sum0 += array[j] + array[j+1]; 
            sum1 += array[j+2] + array[j+3];
            sum2 += array[j+4] + array[j+5]; 
            sum3 += array[j+6] + array[j+7];
        }

        // ... and this one. But your inner loop must do the same
        // number of additions as this one does.

        }

    // You can add some final code between this comment ...
    sum = sum0 + sum1 + sum2 + sum3;
    // ... and this one.

    return 0;
}

ANSWER

&#39;时间&#39;我们用来判断等级的应用程序有点偏差。我能做的最好的是4.9~通过展开循环50次并使用TomKarzes的基本格式对其进行分组。

int     j;
        for (j = 0; j < ARRAY_SIZE; j += 50) {
            sum +=(((((((array[j] + array[j+1]) + (array[j+2] + array[j+3])) +
                    ((array[j+4] + array[j+5]) + (array[j+6] + array[j+7]))) + 
                    (((array[j+8] + array[j+9]) + (array[j+10] + array[j+11])) +
                    ((array[j+12] + array[j+13]) + (array[j+14] + array[j+15])))) +
                    ((((array[j+16] + array[j+17]) + (array[j+18] + array[j+19]))))) +
                    (((((array[j+20] + array[j+21]) + (array[j+22] + array[j+23])) +
                    ((array[j+24] + array[j+25]) + (array[j+26] + array[j+27]))) + 
                    (((array[j+28] + array[j+29]) + (array[j+30] + array[j+31])) +
                    ((array[j+32] + array[j+33]) + (array[j+34] + array[j+35])))) +
                    ((((array[j+36] + array[j+37]) + (array[j+38] + array[j+39])))))) + 
                    ((((array[j+40] + array[j+41]) + (array[j+42] + array[j+43])) +
                    ((array[j+44] + array[j+45]) + (array[j+46] + array[j+47]))) + 
                    (array[j+48] + array[j+49])));
        }

2 个答案:

答案 0 :(得分:2)

我试验了一下分组。在我的机器上,使用我的gcc,我发现以下效果最佳:

    for (j = 0; j < ARRAY_SIZE; j += 16) {
        sum = sum +
              (array[j   ] + array[j+ 1]) +
              (array[j+ 2] + array[j+ 3]) +
              (array[j+ 4] + array[j+ 5]) +
              (array[j+ 6] + array[j+ 7]) +
              (array[j+ 8] + array[j+ 9]) +
              (array[j+10] + array[j+11]) +
              (array[j+12] + array[j+13]) +
              (array[j+14] + array[j+15]);
    }

换句话说,它被展开了16次,它将总和分组成对,然后它线性地添加对。我还移除了+=运算符,该运算符会影响添加中首次使用sum的时间。

我发现即使没有改变任何东西,测量的时间也会从一次运行到另一次运行显着变化,因此我建议对每个版本进行多次计时,然后再对时间是否有所改善或变得更糟而作出任何结论。

我很想知道你使用这个版本的内循环在你的机器上获得了什么数字。

更新:这是我当前最快的版本(在我的机器上,使用我的编译器):

    int     j1, j2;

    j1 = 0;
    do {
        j2 = j1 + 20;
        sum = sum +
              (array[j1   ] + array[j1+ 1]) +
              (array[j1+ 2] + array[j1+ 3]) +
              (array[j1+ 4] + array[j1+ 5]) +
              (array[j1+ 6] + array[j1+ 7]) +
              (array[j1+ 8] + array[j1+ 9]) +
              (array[j1+10] + array[j1+11]) +
              (array[j1+12] + array[j1+13]) +
              (array[j1+14] + array[j1+15]) +
              (array[j1+16] + array[j1+17]) +
              (array[j1+18] + array[j1+19]);
        j1 = j2 + 20;
        sum = sum +
              (array[j2   ] + array[j2+ 1]) +
              (array[j2+ 2] + array[j2+ 3]) +
              (array[j2+ 4] + array[j2+ 5]) +
              (array[j2+ 6] + array[j2+ 7]) +
              (array[j2+ 8] + array[j2+ 9]) +
              (array[j2+10] + array[j2+11]) +
              (array[j2+12] + array[j2+13]) +
              (array[j2+14] + array[j2+15]) +
              (array[j2+16] + array[j2+17]) +
              (array[j2+18] + array[j2+19]);
    }
    while (j1 < ARRAY_SIZE);

这使用的总展开量为40,分为两组,每组20个,具有预先递增的交替诱导变量以打破依赖性,以及后测试循环。同样,您可以尝试使用括号分组来为您的编译器和平台微调它。

答案 1 :(得分:0)

我尝试使用以下方法编写代码:

  • 没有优化,对于整数索引为1的循环,简单sum +=。我的 64位 2011 MacBook Pro上花了16.4秒。

  • gcc -O2,相同的代码,下降到5.46秒。

  • gcc -O3,相同的代码,下降到5.45秒。

  • 我尝试在sum变量中使用带有8向加法的代码。这降低到了2.03秒。

  • 我将它加倍到16位加法到sum变量中,这使它降低到1.91秒。

  • 我把它加倍到sum变量的32路加法。时间到了2.08秒。

  • 我按照@kcraigie的建议切换到指针式方法。使用-O3时,时间为6.01秒。 (对我来说非常惊讶!)

    register double * p;
    for (p = array; p < array + ARRAY_SIZE; ++p) {
        sum += *p;
    }
    
  • 我将for循环更改为while循环,使用sum += *p++并将时间缩短为5.64秒。

  • 我将while循环更改为倒数而不是up,时间上升到5.88秒。

  • 我改回了for循环,增加了8个整数索引,添加了8个寄存器双和[0-7]变量,并在[0中为N添加了_array [j + N]到sumN, 7]。将_array声明为寄存器double * const初始化为array,重要的是它的重要性。这时间缩短到1.86秒。

  • 我改为扩展到10,000个+ _array [n]副本的宏,其中N为常量。然后我做sum = tnKX(addsum)并且编译器因分段错误而崩溃。因此,纯粹的内联方法无法发挥作用。

  • 我切换到一个扩展到10,000 sum += _array[n]个副本的宏,其中N为常量。那跑了6.63秒!显然,加载所有代码的开销会降低内联的效率。

  • 我尝试声明static double _array[ARRAY_SIZE];然后使用__builtin_memcpy在第一个循环之前复制它。通过8路并行添加,这导致2.96秒的时间。我不认为静态数组是要走的路。 (伤心 - 我希望不变的地址会成为赢家。)

从这一切来看,似乎应该是16路内联或8路并行变量。您必须在自己的平台上尝试这一点以确保 - 我不知道更广泛的架构将对数字做什么。

修改

根据@pvg的建议,我添加了以下代码:

int ntimes = 0;

// ... and this one.
...
    // You can change anything between this comment ...

            if (ntimes++ == 0) {

将运行时间减少到&lt; 0.01秒;-)如果你没有被F-stick击中,那就是胜利者。