在c中优化数组循环

时间:2011-05-24 05:57:47

标签: c arrays optimization gcc

我已经在网上看了我的书,但我似乎无法得到这个。 我被要求优化程序的一小部分。特别是在不使用内置优化器的情况下,使用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];    
}

数组值是非常量int s,并且所有值都已初始化。

7 个答案:

答案 0 :(得分:9)

创建子总和,然后加总计。

这是它可能的基本版本

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 :(得分:3)

使用sse / mmx set:

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

答案 2 :(得分:1)

实际上,循环已经由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。

答案 3 :(得分:0)

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

答案 4 :(得分:0)

我不确定为什么你不能使用优化器,因为根据我的经验,它通常会产生比绝大多数“想成为”手动优化器更快的代码:-)此外,您应该确保此代码 实际上是一个问题区域 - 优化已经接近最大速度的代码是没有意义的,您也不应该关注自己占据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 :(得分:0)

由于五个似乎是样本中一次要添加的数量,我也在这里做。正如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 :(得分:0)

通过在滚动循环内预取数据,您可以获得更高的性能 我将以德鲁的答案为基础:

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结合使用。

在网上搜索&#34;数据驱动设计&#34;。