【问题标题】:how to remove an add that's inside the square brackets in a for statement如何删除for语句中方括号内的添加
【发布时间】:2011-05-14 21:19:42
【问题描述】:

我在 C 中有这段代码:

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

    // ---------------------------------------------------------------
    // Do not alter anything above this line

    for (j=0; j < ARRAY_SIZE-7; j=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];
    }
    for(;j<ARRAY_SIZE;j++)
            sum+= array[j];

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

    if (sum != checksum) {
        printf("Checksum error!\n");
    }
    sum = 0;

}

其中 ARRAY_SIZE=9973 和 N_TIMES=200000

这个练习是为了优化。如何删除内部循环中方括号内的添加(例如 array[j+4])?请帮帮我。

【问题讨论】:

  • 您说代码是“C”,但您已将问题标记为“C++”。这是两种不同的语言,解决方案可能会有很大差异。它是哪种语言?
  • 你可以设置一个适当类型的指针p = array[j]然后开始索引ala p[0], p[1] (相当于array[j], array[j + 1])。无论如何都要进行实验和测量,但不要指望这会加速任何事情。甚至您的手动循环展开也是 20 年前的一种技术,无论如何现代编译器都会为您完成。
  • 对数字数组求和的最佳方法是使用向量指令集,如 SSE2。删除添加不会提高性能;无论如何,编译器可能会将内部循环优化为相同的东西。
  • 有趣。这必须是家庭作业:stackoverflow.com/questions/4262285/…

标签: c arrays optimization loops


【解决方案1】:

这是我进行的一些测试的结果。我尝试了 3 个版本的代码,一个没有展开,一个有展开,一个使用 sse 扩展。由于您没有说明数组的类型,因此我使用了整数,顺序为 1..ARRAY_SIZE。

首先是设置的东西,emmintrin.h 是 sse 内在函数的头文件。

#include <stdio.h>
#include <stdlib.h>
#include <emmintrin.h> // SSE2
#include <time.h>

int main(){
    const size_t ARRAY_SIZE = 9973;
    const size_t N_TIMES = 200000;
    int * array = malloc(ARRAY_SIZE*sizeof(*array));
    for (size_t i=0;i<ARRAY_SIZE;++i) array[i] = i;
    clock_t start, stop;

接下来是一个简单的循环。如果优化器很聪明,这应该和其他任何东西一样快,编译器知道您正在将相邻的数组元素相加,并且可以根据需要展开、使用 sse 等。

    // Using normal loop
    start = clock();
    int sum = 0;
    for (size_t i=0;i<N_TIMES;++i){
        for (size_t j=0;j<ARRAY_SIZE;++j){
            sum += array[j];
        }   
    }
    stop = clock();
    printf("normal %d\t%e\n",sum,difftime(stop,start));

下一步比较你的循环

    // Using unwrapped loop
    start = clock();
    sum = 0;
    for (size_t i=0;i<N_TIMES;++i){
        size_t j=0;
        for (;j<ARRAY_SIZE-7;j+=8){
            sum += array[j+0] + array[j+1] + array[j+2] +
                array[j+3] + array[j+4] + array[j+5] + 
                array[j+6] + array[j+7];
        }
        for (;j<ARRAY_SIZE;++j){
            sum += array[j];
        }   
    }
    stop = clock();
    printf("unrolled %d\t%e\n",sum,difftime(stop,start));

最后是一个使用 sse 的循环。 __m128i 类型指定一个 128 位整数数组,必须使用 _mm 内在函数对其进行操作。此代码一次将数组 4 个整数加载到寄存器中,将其添加到求和寄存器,然后在最后解压缩求和寄存器。

    // Using sse
    start = clock();
    sum = 0;
    __m128i sse_sum = _mm_setzero_si128();
    for (size_t i=0;i<N_TIMES;++i){
        size_t j=0;
        for (;j<ARRAY_SIZE-3;j+=4){
            __m128i slice = _mm_load_si128((__m128i*)(array+j));
            sse_sum = _mm_add_epi32(sse_sum,slice);
        }
        for (;j<ARRAY_SIZE;++j){
            sum += array[j];
        }   
    }
    int sse_result[4];
    _mm_store_si128((__m128i*)sse_result,sse_sum);
    for (int i=0;i<4;++i)
        sum += sse_result[i];
    stop = clock();
    printf("sse %d\t%e\n",sum,difftime(stop,start));
}

这是我可用的几个编译器的结果,它们都针对 64 位 OSX 10.6。第一列是循环版本,然后是总和,然后是所用时间:

 $ gcc --version
 i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5664)
 $ gcc loop.c -o loop -msse2 -O3 -std=c99 -ftree-vectorize && ./loop
 normal   -2068657536   7.525430e+05
 unrolled -2068657536   9.638840e+05
 sse      -2068657536   4.929820e+05

$ gcc-4.5 --version
gcc-4.5 (GCC) 4.5.0
$ gcc-4.5 loop.c -o loop -msse2 -O3 -std=c99 -ftree-vectorize && ./loop
normal   -2068657536    9.721320e+05
unrolled -2068657536    9.610700e+05
sse      -2068657536    1.196051e+06

$ clang --version
clang version 2.0 (trunk 103456)
$ clang loop.c -o loop -msse2 -O3 -std=c99 -ftree-vectorize && ./loop
normal   -2068657536    1.155552e+06
unrolled -2068657536    9.613550e+05
sse      -2068657536    1.195248e+06

如您所见,编译器的结果差异很大。在 gcc 中,正常循环看起来得到了很好的优化,性能比展开循环好,几乎与 Apple 编译器中的 sse 一样好,而对于基线 gcc,sse 版本落后了很多(可能是因为 Apple 设置了更好的默认值)编译器的优化)。 Clang 优化的不是很好,虽然我的版本有点旧,展开循环最适合这种情况。

在我看来,最好的方法是使用基本循环,前提是您的编译器执行自动矢量化(这就是 -ftree-vectorize 的用途),因为这是最容易理解的。与手动展开循环相比,您将从摆弄编译器优化级别中获得更多收益。

【讨论】:

    【解决方案2】:

    使用内部循环:

    for (j=0; j < ARRAY_SIZE-7; j=j+8) {
        for (int k = j; k < (k + 8); ++k) {
            sum += array[k];
        }
    }
    

    【讨论】:

    • 问题是 N_TIMES=200000 和 ARRAY_SIZE=9973
    • 首先展开循环(这是原始代码的目的)然后将其更改回内部循环的意义何在?最好有一个循环来汇总所有元素。
    • 只是一个优化练习,我知道它可以在 1 个循环中完成,但这正是问题所在,我必须弄清楚如何从循环中删除该添加以使其更快.
    【解决方案3】:

    使用内部循环。它不会增加算法的复杂性,因为内部循环的范围总是从 0 到 7。如果这真的是 C,则不能创建另一个变量。所以你可以这样做:

    for (j=0; j < ARRAY_SIZE-7; j=j+8) {
        sum += array[j];
        for (array[j] = 0; array[j] < 8; array[j]++) {
            sum += array[array[j]];
        }
    }
    

    我想它会起作用,因为你不再需要array[j]

    【讨论】:

    • 问题是 N_TIMES=200000 和 ARRAY_SIZE=9973 所以我认为它不会让它更快......我在 linux 服务器上的最佳时间是 7.2 秒
    【解决方案4】:

    这看起来像是循环展开的尝试。试试这个吧。

    for (j=0; j < ARRAY_SIZE-7;) {
        sum += array[j++];
        sum += array[j++];
        sum += array[j++];
        sum += array[j++];
        sum += array[j++];
        sum += array[j++];
        sum += array[j++];
        sum += array[j++];
    }
    for(;j<ARRAY_SIZE;j++)
            sum+= array[j];
    

    【讨论】:

      【解决方案5】:

      EDIT2:这个答案现在无效,因为之前它被标记为 C++

      在 C++ 中尝试 std::accumulate

      这是一个例子

      #include <numeric>
      
      int main(){
         int buf[10] = {7, 8, 1, 9, 5, 0, 2, 3, 6, 4};
      
         int sum = std::accumulate (buf, buf+7, 0);
      }
      

      【讨论】:

      • @James:它是在 STL 中实现的,并且通过运行相同的循环来进行求和,如果我们自己实现它,你和我会运行它
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-05-02
      • 2020-11-16
      • 1970-01-01
      • 2020-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多