【问题标题】:optimizing array loop in c在c中优化数组循环
【发布时间】:2011-08-31 16:01:52
【问题描述】:

我在网上和我的书中都看过,但我似乎无法理解。 我被要求优化程序的一小部分。特别是在不使用内置优化器的情况下,使用 vi 和 gcc 在很短的时间内获取一个数组并添加其内容。 我已经尝试过循环展开和其他一些针对产品的优化。你能帮忙吗?

int length = ARRAY_SIZE;
int limit = length-4;
for (j=0; j < limit; j+=5) {
    sum += array[j] + array[j+1] + array[j+2] + array[j+3] + array[j+4];
}
for(; j < length; j++){
    sum += array[j];    
}

数组值为非常量ints,所有值都已初始化。

【问题讨论】:

  • 你可以使用线程或fork吗?
  • 您如何衡量各种替代方案的性能?您是否调查过指针而不是数组下标是否可以改善事情?你有没有调查过这段代码是否真的是程序中的瓶颈?
  • 按 5 展开是不好的。请尝试 4 或 8。用-msse4编译
  • 听起来像是复制品。虽然找不到原版...
  • @newb @missingno 这是一个相关的问题:stackoverflow.com/questions/5952636/…

标签: c arrays optimization gcc


【解决方案1】:

一种解决方案是始终保持总和。当然,每次更改数组中的值时都必须更新它,但如果不发生这种情况,那通常可能值得麻烦。

【讨论】:

    【解决方案2】:

    使用 sse/mmx 集:

    __m128i sum;
    for (j=0; j < limit; j+=4) {
        sum = _mm_add_epi32(sum, array+j);
    }
    

    【讨论】:

    • 我会假设,因为这是家庭作业,他应该只使用 C 而不是寻找疯狂的处理器指令。
    • 自动向量化无论如何都会这样做,并且它会正确处理未对齐的数组。
    • 我认为你不应该假设自动矢量化。
    【解决方案3】:

    创建子总和,然后相加为总和。

    这是它的基本版本

    for (j=0; j < limit; j+=4) {
        sum1 += array[j];
        sum2 += array[j+1];
        sum3 += array[j+2];
        sum4 += array[j+3];
    }
    sum = sum1 + sum2 + sum3 + sum4;
    

    这避免了一些先读后写的依赖——也就是说,每次循环迭代中 sum2 的计算不需要等待 sum1 的结果来执行,处理器可以同时调度循环中的两条线。

    【讨论】:

    • +1:循环展开通常对现代 CPU 没有帮助,但像这样打破简单循环中的依赖关系可能是有益的
    【解决方案4】:

    我不确定为什么你不能使用优化器,因为根据我的经验,它通常会比绝大多数“想要成为”手动优化器产生更快的代码 :-)此外,您应该确保此代码 实际上是一个问题区域 - 没有必要优化已经接近最大速度的代码,也不应该担心占时间的 0.01%在其他地方可能有代码负责 20% 时采取。

    优化应该有针对性,否则就是白费。

    除了天真的“只是将数字相加”之外的任何解决方案很可能都必须使用目标 CPU 中的特殊功能。


    如果您愿意对数组的每个 更新 进行小幅调整(鉴于您的“所有值都已初始化”注释,这可能不是一个选项),您可以获得在非常快的时间内求和。使用“类”来并排维护数组和总和。伪代码如:

    def initArray (sz):
        allocate data as sz+1 integers
        foreach i 0 thru sz:
            set data[i] to 0
    
    def killArray(data):
        free data
    
    def getArray (data,indx):
        return data[indx+1]
    
    def setArray (data,indx,val):
        data[0] = data[0] - data[indx] + val
        data[indx+1] = val
    
    def sumArray(data):
        return data[0]
    

    应该可以解决问题。


    以下完整的 C 程序显示了一个非常粗略的初步方案,您可以将其用作更强大解决方案的基础:

    #include <stdio.h>
    #include <stdlib.h>
    
    static int *initArray (int sz) {
        int i;
        int *ret = malloc (sizeof (int) * (sz + 1));
        for (i = 0; i <= sz; i++)
            ret[i] = 0;
        return ret;
    }
    
    static void killArray(int *data) {
        free (data);
    }
    
    static int getArray (int *data, int indx) {
        return data[indx+1];
    }
    
    static void setArray (int *data, int indx, int val) {
        data[0] = data[0] - data[indx] + val;
        data[indx+1] = val;
    }
    
    static int sumArray (int *data) {
        return data[0];
    }
    

     

    int main (void) {
        int i;
        int *mydata = initArray (10);
        if (mydata != NULL) {
            setArray (mydata, 5, 27);
            setArray (mydata, 9, -7);
            setArray (mydata, 7, 42);
            for (i = 0; i < 10; i++)
                printf ("Element %d is %3d\n", i, getArray (mydata, i));
            printf ("Sum is %3d\n", sumArray (mydata));
        }
        killArray (mydata);
        return 0;
    }
    

    这个的输出是:

    Element 0 is   0
    Element 1 is   0
    Element 2 is   0
    Element 3 is   0
    Element 4 is   0
    Element 5 is  27
    Element 6 is   0
    Element 7 is  42
    Element 8 is   0
    Element 9 is  -7
    Sum is  62
    

    正如我所说,这可能不是一个选项,但是,如果你可以摆动它,你将很难找到比单个数组索引提取更快的方法来获得总和。


    而且,只要您正在实现一个类来执行此操作,您还可以使用前 两个 元素进行内务处理,一个用于当前总和,一个用于最大索引,所以您可以通过检查indx 与最大值来避免越界错误。

    【讨论】:

    • 在我看来,作业是为了演示优化器的工作,让学生去做。
    【解决方案5】:

    由于在示例中一次添加的数量似乎是五个,所以我在这里也这样做。通常,您按照 Drew Hoskins 的建议使用 2 的幂。 通过在开始时正确取模并朝另一个方向迈进,可能需要更少的值。 以不同的顺序计算在科学计算中通常是有利可图的,而不仅仅是索引。 要了解优化是否以及有多好,测试是必不可少的。

    int sum1, sum2, sum3, sum4;
    
    for(j = ARRAY_SIZE; j%5; j--){
        sum += array[j]; 
    }
    sum1 = sum2 = sum3 = sum4 = 0;
    for (; j; j-=5) {
        sum += array[j-1];
        sum1 += array[j-2];
        sum2 += array[j-3];
        sum3 += array[j-4];
        sum4 += array[j-5];
    }
    sum += sum1+sum2+sum3+sum4;
    

    【讨论】:

      【解决方案6】:

      事实上,循环已经展开了 5。

      由于您要禁用优化器,所有这些索引都会让您付出代价。

      第一个循环可以替换为:

      int* p = array;
      for (j = 0; j < ARRAY_SIZE - 4; j += 5, p += 5){
        sum += p[0] + p[1] + p[2] + p[3] + p[4];
      }
      

      所以它没有做任何索引(将j 乘以sizeof(int) 并将其添加到地址中)。

      补充:当然,因为 ARRAY_SIZE 大概是一个已知常量,这可能是最快的代码,但你可能需要编写代码生成器(或聪明的宏)来制作它:

      sum += array[0];
      sum += array[1];
      ...
      sum += array[ARRAY_SIZE - 1];
      

      此类宏的一个示例是,如果 ARRAY_SIZE 是 2 的幂,例如 64,您可以:

      #define FOO64(i) FOO32(i); FOO32((i)+32)
      #define FOO32(i) FOO16(i); FOO16((i)+16)
      #define FOO16(i) FOO8(i); FOO8((i)+8)
      #define FOO8(i) FOO4(i); FOO4((i)+4)
      #define FOO4(i) FOO2(i); FOO2((i)+2)
      #define FOO2(i) FOO1(i); FOO1((i)+1)
      #define FOO1(i) sum += array[i]
      
      FOO64(0);
      

      你可以对其他幂做同样的想法,比如 10。

      【讨论】:

        【解决方案7】:

        通过在滚动循环中预取数据,您可以获得更高的性能。
        我会以 Drew 的回答为基础:

        register int value1, value2, value3, value4;
        or (j=0; j < limit; j+=4)
        {
            // Prefetch the data
            value1 = array[j];
            value2 = array[j + 1];
            value3 = array[j + 2];
            value4 = array[j + 4];
        
            // Use the prefetched data
            sum1 += value1;
            sum2 += value2;
            sum3 += value3;
            sum4 += value4;
        }
        sum = sum1 + sum2 + sum3 + sum4;
        

        这里的想法是让处理器将连续数据加载到它的缓存中,然后对缓存的数据进行操作。为了使其有效,编译器不得优化预取;这可以通过将临时变量声明为volatile 来执行。不知道volatile能不能和register合并。

        在网上搜索“数据驱动设计”。

        【讨论】:

          猜你喜欢
          • 2011-11-08
          • 2011-07-01
          • 1970-01-01
          • 2014-07-30
          • 2017-02-28
          • 2013-05-30
          • 2018-12-23
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多