在c中优化嵌套的for循环

时间:2012-01-21 08:37:39

标签: c optimization for-loop nested

我有以下一段c代码,

double findIntraClustSimFullCoverage(cluster * pCluster)
{
    double sum = 0;
    register int i = 0, j = 0;
    double perElemSimilarity = 0;

    for (i = 0; i < 10000; i++)
    {
        perElemSimilarity = 0;

        for (j = 0; j < 10000; j++)
        {

            perElemSimilarity += arr[i][j];

        }
        perElemSimilarity /= pCluster->size;
        sum += perElemSimilarity;
    }
    return (sum / pCluster->size);
}

NOTE: arr是一个大小为10000 X 10000

的矩阵

这是GA代码的一部分,因此这个嵌套for循环运行多次。 这会影响代码的性能,即花费大量时间来提供结果。 我使用valgrind / kcachegrind分析了代码。 这表明70%的进程执行时间用于运行此嵌套for循环。 寄存器变量i和j似乎没有存储在寄存器值中(使用和不使用&#34进行分析;寄存器&#34;关键字表示这个)

我根本找不到一种方法来优化这个嵌套的for循环部分代码(因为它非常简单直接)。 请帮我优化这部分代码。

5 个答案:

答案 0 :(得分:1)

我假设您经常更改arr矩阵,否则您只需计算一次总和(请参阅Lucian的答案)并记住它。

修改矩阵时可以使用类似的方法。在矩阵(可能)已经改变之后,不是完全重新计算总和,而是可以在某处存储“和”值,并且让更新矩阵的每一段代码都适当地更新存储的总和。例如,假设您从一个全零的数组开始:

double arr[10000][10000];
< initialize it to all zeros >
double sum = 0;

// you want set arr[27][53] to 82853
sum -= arr[27][53];
arr[27][53] = 82853;
sum += arr[27][53];

// you want set arr[27][53] to 473
sum -= arr[27][53];
arr[27][53] = 473;
sum += arr[27][53];

您可能希望不时地完全重新计算总和,以避免累积错误。

答案 1 :(得分:0)

我可能在这里错了,但不是以下等价物:

for (i = 0; i < 10000; i++)
{
    for (j = 0; j < 10000; j++)
    {
        sum += arr[i][j];
    }
}
return (sum / ( pCluster->size * pCluster->size ) );

答案 2 :(得分:0)

  1. register关键字是一个优化器提示,如果优化器不认为寄存器在那里花了很多,它就不会。
  2. 矩阵是否包装良好,即它是一块连续的内存块吗?
  3. 'j'是次要索引(即你是从内存中的一个元素转到另一个元素),还是从一个元素跳到那个加上1000?
  4. arr是否相当静态?这是否在同一个arr上被多次调用?内循环的结果仅取决于j遍历的行/列,因此懒惰地计算它并将其存储以供将来参考将产生很大的差异

答案 3 :(得分:0)

如果您确定无法进行算法优化,则必须依靠非常低级别的优化来加速代码。这些是特定于平台/编译器的,因此您的里程可能会有所不同。

在某些时候,操作的瓶颈可能是从内存中提取arr的值。因此,请确保您的数据以线性缓存友好的方式布局。也就是说&arr[i][j+1] - &arr[i][j] == sizeof(double)

如果您的编译器尚未执行此操作,您也可以尝试展开内部循环。你的代码:

    for (j = 0; j < 10000; j++)
    {
        perElemSimilarity += arr[i][j];
    }

例如会变成:

    for (j = 0; j < 10000; j+=10)
    {
        perElemSimilarity += arr[i][j+0];
        perElemSimilarity += arr[i][j+1];
        perElemSimilarity += arr[i][j+2];
        perElemSimilarity += arr[i][j+3];
        perElemSimilarity += arr[i][j+4];
        perElemSimilarity += arr[i][j+5];
        perElemSimilarity += arr[i][j+6];
        perElemSimilarity += arr[i][j+7];
        perElemSimilarity += arr[i][j+8];
        perElemSimilarity += arr[i][j+9];
    }

这些是基本思想,如果不了解您的平台,编译器,查看生成的汇编代码,很难说更多。

您可能需要查看this presentation以获取有关优化机会的更完整示例。

如果您需要更高的性能,可以查看适用于您的平台的SIMD内在函数,尝试使用OpenMP,在多个线程上分发计算。


另一个步骤是尝试使用OpenMP,以下内容(未经测试):

#pragma omp parallel for private(perElemSimilarity) reduction(+:sum)
for (i = 0; i < 10000; i++)
{
    perElemSimilarity = 0;
    /* INSERT INNER LOOP HERE */
    perElemSimilarity /= pCluster->size;
    sum += perElemSimilarity;
}

但是请注意,即使您将这部分代码带到执行时间的0%(这是不可能的),您的GA算法仍然需要数小时才能运行。现在,您的性能瓶颈在于其他部分代码仅占用了22%的运行时间。

答案 4 :(得分:0)

说明这个问题的方法,你无能为力。您正在处理10,000 x 10,000双输入值,即800 MB。无论你做什么都受限于读取800 MB数据所需的时间。

另一方面,你每次调用它时还要写10,000 x 10,000个值吗?如果没有,您可以例如存储每行的总和,并且有一个布尔行,表示需要计算行总和,每次更改行元素时都会设置行总和。或者您甚至可以在每次更改数组元素时更新行的总和。