C 中双精度数组的优化和[重复]

2023-12-19

我有一项任务,我必须完成一个程序并使其在时间上更有效率。 原来的代码是:

#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 ...
    long int help;
    // ... and this one.

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

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

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

        int     j;

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

        // ... 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 ...

    // ... and this one.

    return 0;
}

我几乎完全修改了第二个 for 循环,将其更改为

double *j=array;
double *p=array+ARRAY_SIZE;

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

这本身就能够将时间减少到标准......它似乎已经起作用了但有什么我没有看到的错误吗?


我在这个答案的副本上发布了这个答案的改进版本:最终分配的 C 循环优化帮助 https://stackoverflow.com/questions/32000917/c-loop-optimization-help-for-final-assignment。最初只是转发,但后来我做了一些修改来回答该问题的差异。我忘记了有什么不同,但你可能应该读一下那个。也许我应该删除这个。

另请参阅中的其他优化指南x86 /questions/tagged/x86标签维基。


首先,它是一个非常糟糕的示例,因为它没有任何东西可以阻止智能编译器优化整个事情。它甚至不打印总和。甚至gcc -O1(代替-O3)丢弃了一些循环。

通常,您会将代码放入函数中,然后在循环中调用它main()在另一个文件中。并单独编译它们,没有整个程序的跨文件优化,因此编译器无法根据您调用它的编译时常量进行优化。重复循环如此紧密地包裹在数组上的实际循环周围,对 gcc 的优化器造成了严重破坏(见下文)。

Also:

gcc -Wall -O3 -march=native  fast-loop-cs201.c -o fl
fast-loop-cs201.c: In function ‘main’:
fast-loop-cs201.c:17:14: warning: ‘help’ is used uninitialized in this function [-Wuninitialized]
     long int help; 

我不得不同意 EOF 对你教授的贬低言论。给出没有优化的代码,并且带有未初始化的变量,这完全是无稽之谈。

有些人在评论中说“编译器并不重要”,您应该针对 CPU 微体系结构优化 C 源代码,而不是让编译器来做。这很糟糕:为了获得良好的性能,您必须了解编译器可以做什么和不能做什么。有些优化是“脆弱的”,对源代码进行看似无害的小更改将阻止编译器执行某些操作。

我想你的教授提到了一些关于性能的事情。有很多不同的东西可以在这里发挥作用,其中许多我认为在二年级的计算机科学课程中没有被提及。

除了使用 openmp 进行多线程处理之外,还有使用 SIMD 进行矢量化。还有针对现代流水线 CPU 的优化:具体来说,避免拥有一个长依赖链。

进一步的重要阅读:

  • 阿格纳·福格的指南 http://agner.org/optimize/用于针对 x86 优化 C 和 asm。其中一些适用于所有 CPU。
  • 每个程序员都应该了解的内存知识 http://www.akkadia.org/drepper/cpumemory.pdf

您的编译器手册也很重要,尤其是。对于浮点代码。浮点数的精度有限,not联想的。最后总和does取决于您进行加法的顺序。但是,通常舍入误差的差异很小。因此,如果您使用,编译器可以通过重新排序来获得很大的加速-ffast-math允许它。这可能是您的 unroll-by-10 所允许的。

不只是展开,保留多个累加器(仅在最后相加)可以使浮点执行单元保持饱和,因为 FP 指令具有延迟!=吞吐量。如果您需要上一个操作的结果在下一个操作开始之前完成,那么您会受到延迟的限制。对于 FP 添加,即每 3 个周期一次。在 Intel Sandybridge、IvB、Haswell 和 Broadwell 中,FP add 的吞吐量为每周期 1 个。因此,您需要保留至少 3 个可以同时运行的独立操作,以使机器饱和。, it's 每周期 2 个,延迟为 4 个时钟 http://users.atw.hu/instlatx64/GenuineIntel00506E3_Skylake_InstLatX64.txt。 (对于 Skylake 来说,好的一面是,FMA 可将延迟降低至 4 个周期。)

在这种情况下,还有一些基本的东西,比如将事情从循环中拉出来,例如help += ARRAY_SIZE.

编译器选项

我从原来的内循环开始,只是help += ARRAY_SIZE拉出来,并添加一个printf最后,所以 gcc 不会优化所有内容。让我们尝试一些编译器选项,看看我们可以使用 gcc 4.9.2 实现什么(在我的i5 2500k 桑迪布里奇 http://ark.intel.com/products/52210/Intel-Core-i5-2500K-Processor-6M-Cache-up-to-3_70-GHz。 3.8GHz 最大睿频(轻微超频),持续 3.3GHz(与这个简短的基准测试无关):

  • gcc -O0 fast-loop-cs201.c -o fl:16.43s 的性能完全是个笑话。每次操作后变量都会存储到内存中,并在下一次操作之前重新加载。这是一个瓶颈,并且会增加很多延迟。更不用说失去实际的优化了。定时/调谐代码-O0没有用。
  • -O1: 4.87s
  • -O2: 4.89s
  • -O3:2.453s(使用SSE一次执行2个。我当然使用64位系统,所以硬件支持-msse2是基线。)
  • -O3 -ffast-math -funroll-loops: 2.439s
  • -O3 -march=sandybridge -ffast-math -funroll-loops:1.275s(使用 AVX 一次执行 4 个操作。)
  • -Ofast ...: 没有收获
  • -O3 -ftree-parallelize-loops=4 -march=sandybridge -ffast-math -funroll-loops:0m2.375s真实,0m8.500s用户。看起来锁定开销杀死了它。它总共只生成 4 个线程,但内部循环太短,无法获胜(因为它每次都会收集总和,而不是为一个线程提供外部循环迭代的前 1/4)。
  • -Ofast -fprofile-generate -march=sandybridge -ffast-math,运行它,然后
    -Ofast -fprofile-use -march=sandybridge -ffast-math: 1.275s

  • clang-3.5 -Ofast -march=native -ffast-math:1.070秒。 (clang不支持-march=sandybridge).

gcc -O3以一种搞笑的方式进行向量化:内部循环并行执行外部循环的 2(或 4)次迭代,方法是将一个数组元素广播到 xmm(或 ymm)寄存器的所有元素,并执行addpd关于这一点。所以它看到相同的值被重复添加,但即使-ffast-math不让 gcc 把它变成乘法。或者切换循环。

clang-3.5 的矢量化效果更好:它矢量化内部循环,而不是外部循环,因此不需要广播。它甚至使用 4 个向量寄存器作为 4 个独立的累加器。然而,它并不假设calloc返回对齐的内存,并且出于某种原因,它认为最好的选择是一对 128b 加载。

vmovupd -0x60(%rbx,%rcx,8),%xmm4`
vinsertf128 $0x1,-0x50(%rbx,%rcx,8),%ymm4,%ymm4

其实是slower当我告诉它数组已对齐时。 (用一个愚蠢的黑客,比如array = (double*)((ptrdiff_t)array & ~31);它实际上生成一条指令来屏蔽低 5 位,因为 clang-3.5 不支持 gcc__builtin_assume_aligned.) 我认为 4x 的紧密循环方式vaddpd mem, %ymmX,%ymmX对齐看跌期权cmp $0x271c,%rcx跨越 32B 边界,因此它不能与jne。不过,uop 吞吐量不应该成为问题,因为根据该代码,每个周期仅获得 0.65insns(以及每个周期 0.93 uops)perf.

啊,我用调试器检查过,然后calloc仅返回 16B 对齐的指针。因此,一半的 32B 内存访问会跨越缓存行,从而导致速度大幅下降。我猜is在 Sandybridge 上,当您的指针是 16B 对齐但不是 32B 对齐时,执行两个单独的 16B 加载会稍微快一些。编译器在这里做出了一个很好的选择。

源级别变化

正如我们从 clang 击败 gcc 中看到的,多个累加器非常出色。最明显的方法是:

for (j = 0; j < ARRAY_SIZE; j+=4) {  // unroll 4 times
    sum0 += array[j];
    sum1 += array[j+1];
    sum2 += array[j+2];
    sum3 += array[j+3];
}

然后直到外循环结束后才将 4 个累加器合为一个。

您的源更改为

sum += j[0]+j[1]+j[2]+j[3]+j[4]+j[5]+j[6]+j[7]+j[8]+j[9];

由于乱序执行,实际上具有类似的效果。每组 10 个都是一个单独的依赖链。操作顺序规则表示j值首先相加,然后添加到sum。因此,循环携带的依赖链仍然只是一个 FP add 的延迟,并且每组 10 个都有大量独立的工作。每个组都是一个独立的 9 个 add 依赖链,并且只需要很少的指令即可完成-命令执行硬件以查看下一条链的开始,并找到并行性以保持那些中等延迟、高吞吐量的 FP 执行单元。

With -O0,正如您愚蠢的作业显然需要的那样,值在每个语句末尾存储到 RAM 中。 (从技术上讲,在每个“序列点”,如 C 标准所称。)编写更长的表达式而不更新任何变量,甚至是临时变量,将使-O0运行得更快,但这不是一个有用的优化。不要把时间浪费在那些改变上only帮助-O0,特别是。不以牺牲可读性为代价。


使用 4 个累加器,并且直到外循环结束时才将它们加在一起,从而使 clang 的自动矢量化器失效。它的运行时间仍然仅为 1.66 秒(而 gcc 的非矢量化则为 4.89 秒)-O2与一个累加器)。甚至gcc -O2没有-ffast-math此源更改也需要 1.66 秒。请注意,已知 ARRAY_SIZE 是 4 的倍数,因此我没有包含任何清理代码来处理最后最多 3 个元素(或者避免读取超出数组末尾的内容,这将按照现在的方式发生) 。执行此操作时,很容易出错并读到数组末尾。

另一方面,gcc 确实对此进行了矢量化,但它也将内部循环悲观(未优化)为单个依赖链。我认为它再次对外循环进行多次迭代。


使用 gcc 的平台无关向量扩展,我编写了一个可以编译成明显最佳代码的版本:

// compile with gcc -g -Wall -std=gnu11 -Ofast -fno-tree-vectorize -march=native fast-loop-cs201.vec.c -o fl3-vec

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <assert.h>
#include <string.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 ...
    long int help = 0;

    typedef double v4df __attribute__ ((vector_size (8*4)));
    v4df sum0={0}, sum1={0}, sum2={0}, sum3={0};

    const size_t array_bytes = ARRAY_SIZE*sizeof(double);
    double *aligned_array = NULL;

    // this more-than-declaration could go in an if(i == 0) block for strict compliance with the rules
    if ( posix_memalign((void**)&aligned_array, 32, array_bytes) ) {
        exit (1);
    }
    memcpy(aligned_array, array, array_bytes);  // In this one case: faster to align once and have no extra overhead for N_TIMES through the loop

    // ... and this one.

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

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

        // You can change anything between this comment ...
    /*
    #if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 // GCC 4.7 or later.
        array = __builtin_assume_aligned(array, 32);
    #else
        // force-align for other compilers.  This loop-invariant will be done outside the loop.
        array = (double*) ((ptrdiff_t)array & ~31);
    #endif
    */

        assert ( ARRAY_SIZE / (4*4) == (ARRAY_SIZE+15) / (4*4) );  // We don't have a cleanup loop to handle where the array size isn't a multiple of 16


        // incrementing pointers can be more efficient than indexing arrays
        // esp. on recent Intel where micro-fusion only works with one-register addressing modes
        // of course, the compiler can always generate pointer-incrementing asm from array-indexing source
        const double *start = aligned_array;

        while ( (ptrdiff_t)start & 31 ) {
            // annoying loops like this are the reason people use aligned buffers
            sum += *start++;        // scalar until we reach 32B alignment
            // in practice, this loop doesn't run, because we copy into an aligned buffer
            // This will also require a cleanup loop, and break our multiple-of-16 doubles assumption.
        }

        const v4df *end = (v4df *)(aligned_array+ARRAY_SIZE);
        for (const v4df *p = (v4df *)start ; p+3 < end; p+=4) {
            sum0 += p[0];   // p+=4 increments the pointer by 4 * 4 * 8 bytes
            sum1 += p[1];       // make sure you keep track of what you're incrementing
            sum2 += p[2];
            sum3 += p[3];

        }

        // the compiler might be smart enough to pull this out of the inner loop
        // in fact, gcc turns this into a 64bit movabs outside of both loops :P
        help+= ARRAY_SIZE;

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

        /* You could argue legalese and say that
         if (i == 0) {
             for (j ...)
                 sum += array[j];
             sum *= N_TIMES;
         }
         * still does as many adds in its *INNER LOOP*, but it just doesn't run it as often
         */
    }

    // You can add some final code between this comment ...
    sum0 = (sum0 + sum1) + (sum2 + sum3);
    sum += sum0[0] + sum0[1] + sum0[2] + sum0[3];
    printf("sum = %g; help=%ld\n", sum, help);  // defeat the compiler.

    free (aligned_array);
    free (array);  // not strictly necessary, because this is the end of main().  Leaving it out for this special case is a bad example for a CS class, though.
    // ... and this one.

    return 0;
}

内循环编译为:

  4007c0:       c5 e5 58 19             vaddpd (%rcx),%ymm3,%ymm3
  4007c4:       48 83 e9 80             sub    $0xffffffffffffff80,%rcx   # subtract -128, because -128 fits in imm8 instead of requiring an imm32 to encode add $128, %rcx
  4007c8:       c5 f5 58 49 a0          vaddpd -0x60(%rcx),%ymm1,%ymm1   # one-register addressing mode can micro-fuse
  4007cd:       c5 ed 58 51 c0          vaddpd -0x40(%rcx),%ymm2,%ymm2
  4007d2:       c5 fd 58 41 e0          vaddpd -0x20(%rcx),%ymm0,%ymm0
  4007d7:       4c 39 c1                cmp    %r8,%rcx  # compare with end with p
  4007da:       75 e4                   jne    4007c0 <main+0xb0>

(欲了解更多,请参阅 godbolt 上的在线编译器输出 https://gcc.godbolt.org/#z:OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAKxAEZSBnVAV2OUxAHIB6bgajQBbAA54ANpj4B3Qgj7BkyPgFpgKgOoBDMWJUMC6AKQBmACLAAds1q0VAeQBmm/SocXUygsUyZlAN0xkImI8AC9JZUFNNgQTUwtNAjwAvid9ZTFUVGFlZAYAJgAGWgA6AOQSpWVUVLFjf0DDQoBBJtb84zwLZDFmLD4TAGF9fFQS2ONsNsMOrp6%2BySGRsTwAI3GTKZaZzu7e/qWDLAcNyenZvYWB40HnBkxiAlOt9t35g5v9EItgZ%2BmW3h8ACaLD40UkqAsYgAnmCdKgpJh0HwiHwogBrSTIBCaH6YBgomoEBB4AlofrOPgMYSBPAOPBIvirWHErGoQSCTAWAgErp8Qglf7NQEAFQQbP60NBDGYq0EhDRzBcOJSrPuKKkNT82mY%2BMF2w6xy6kgAcgB9EUASQAstgAMp8R2OgBshTdbvOxiNFkkzQASn7mkCzXbLQAtbBO2juwpCroENGaLoQPyoPDoACU0wA7AAhNpOvjoFirCSOgBU0WImlhcT4EGLsok5YzyG0mWQEH9geDoYjpCpYUwqAcDZLEgzWeM%2BZahcbpckVOYgmupj4senBad8cL/JMM9as6dgJBzAEuLB6GR2pCmgXBNWmAIiK5KJJZPZnO5fBKv63jsyH5%2BW/cUxGEVd133IVCwIaEaWOItx0kPwABZ0AcPgzTNRIvDWZgCEwLD6xTQJgjNBgh3rAAOcsUMnKcD0LVD0KXQQN1MQw81jbNTAHGVBFoOJONzbjeNY/IhK4zixP44xJJE6SoO2I9HTQCwXAo8IzQTKsazNZkCIJOtuyDENw2wctNOHUd5wnJTmjnJC%2BErFZLCRbDiGrWszD4E0AFUABkAvs/8%2BEBYlSTREhfGJXFlCwHpokSPBIQEFgxGRYAaj5C86QgPBVzrQoMyZDt0VSEgqVwoI0pEFZcXYaRZDfSRiGYCQGFCul6z4YRUAogAPM1OSiVyLCgVN03LFsZmdbQ8Dc9APK8gdjHyAddOhfToUMkqSuE/4AE5MAGhUIFoBjQsUlS0UwQRkGEaEIHmxblprDbPPesFPq2gz8QYx1AUtCw30iyEsWcDhUmcAjiEJOEFpByFGtxZEVUkdw%2BBOrxND4VAAmIcVNGRBxKvNK1bQdYliBYYA5FZPhMmyaDj34X8SjBCxkQiglwf1Q8HNZvgAAUJEhgQcTxPgwE4KVWD4BJORl%2BG5bhzQgmYbQFc0Tl%2BcLYRvgIUcZnyQY7SKWxlD4ZoGGAQQExQlQ%2BEtDmADESCyghDAAVkGCwTcu5TBcdUm4fyiCN1zflrkGXyLRte19z3fJ8xT/a8xZ3cwv4U9zxB7FcWASRcR2kkgMfZ8fBBnnaq/BN2babhy09brvSRCAsIAcX8wYsP2/JnVm%2Bsu57ojyz4aNCgGFPMLNbu/N761LRNOw/T764ph8lDCmzbO%2BE7wZY5Qkpd8qsREgefmmkOzaIKw1ZmHEJILGwhh%2BMIl6fXQZ6ftW/JA9eJgMQ9wjqAlDuwZQn8Kpw1QKyOGQhRASGIAwDmfAxSRSZjkLoN48C4gTDIHQTJJDFh9HjfCFF%2BgM0wVfQoN8foQTHE2TALZiLCC8PgBwDhtIZlvkPAAfsYC6IUOhcg4W0cs3AhTXzuA8BMEBrYBlMn2SM/AIAoVovtMwdYuyKN7OZGYuZaDexKqo9RdE%2BAAz3uoYhkIZYJnRmCAQYsrDgUwfDSW6AyxSHFN4Fq30vKDnCPyBgFg7GOMEO1JIwgywjgnoPIOoD%2BBzG8HXLoag%2BrxgeGSC8j4oq%2BMwJwvAyAGTfliiDLoWBTpAU2p1ZSh1AT4mEBzVK3h2DfmBgRXQ3iHiSHlMgGmygHBKhSkjKEsJNTEHRASGQxI8Y%2BmUN4YApJYaXnQN4N%2BaSopYFqYeep/BYloFYPcAcDMEHiAeHnOEUgawEiLj6asBFeppm5A8ZQyS7pcmfmoZwK4HA0xXJtN5XMTqbKYKwdgR01IuFspISyBBogJjrJ/dym0Qp1O8ecnqEA2HEA4VwggGZ9AIuns6PggiLEDAznUwsgJcTuGhJszBBIViYlBgScEfjvDOFSjSbIZYlTFzGoyB%2BnCslHULPxae%2BYfJwoRQY%2BVm5aE0v4AwNs584bMG5OIaQrVMDqzkGtKOn867iqFnyA26skjsBOe%2BRmWRwLFnxKEzgCY2oWAHI%2BNsAqdVpUesBVEF5kXIhFQ4B4prAb8HQdM8QuhtBMD4N4AAjo/XxuMeh6pcXa7IG0uZMi5eVFgcMIliCiRIaoDhlC0FJTC9lb9lxsJGTQw610BbXyhQmZiGFyyiIYZ25yGZnpCqWqilOJk9ERgAVnEOlUIDtr4H28s4FtELsJfCx41wo7CAMcYGOWMuZJy3SnOI5iDrUqdPxKeCq1xbu9gpb2HFpyOmvoCQ90rTCO3eXXAkDMMkvLhsyPgjtx5Ab4FRJkO18ThtYrYK9vUfaGJ9g%2BqOWdAQYkkDKXxcs%2BCYkwOBHGyAC0YW8YkPgcsZa%2BM/Z8tJUH%2BL5ClXWG9uYJL3rRYdCVy4d2wcY3JFjirdktsSX4s5SC0QLQQAmXJDAojrq5LTemNRhDtV0DXFgCZYkMy6PcrNW66mAj5E4IIA4FBKAIKwdSbL/U1Fxs6FCqwFSCHxneXm5D0wQgwqsWBcgmV8BAMLI6oFX3GV0WZCMaLg67kBOzTm3NbV8z4LmfCpHC3AS024iJ0KiTiipDrTAoVlUK2XI%2BGBGFib4CSJCWtFnwaIT1FI2hTdgSgkORlb6wBdSM0wMAbQ%2BJi65oYDWN8iRxXdXDkJHyxVKUHmvruUO9YqA/l/FmM9U7JWwdRbeqgiH7JTqzpK5uPlyYJztKxylHFqXj30DGmrlWogWFhKV3k5SeTOWXiabAfo%2BABTsHYYW5YPWJYVFQJUCZHUhLCW6/kOleYOAIv7c7kiboCZuieJrgarxUnZJIekCRdDkkkBXF81dbVCDrgtvW57lyXp8hAC9UroP9yjjT5cdGDGsWMJOinK5YMXvg7Ge9dOee3sEvz1ngumOIYF5T%2BDPGkOhQNvGY2%2BR8irZ8jMb2wAk4BaEvkb2GUfZ%2BxNnxZcA4AuWMBMcPVCZTnskQZfTOqRvCSEHYjFFP0Och0d/WTaZv%2BDuATF8IpBAYQK0CPifrxBoQesCJob1NdIoMx7bEqIyYMyoICnqvwmyFSqegRZ6ktItZtnVJFXGqxiZYwGjraJWPKq4zNk4u4Nq5Pk4jWT6LVWfQ0PC94UzxAQaRxzGd5onAMykDEFwb2nBSAWC4IUKfqAuCDHlTPMFbBFgdFoFPggs%2BR%2Bj/RCAb2hQx9cBQlPmfnA5%2BkAX5wKfDAQBH%2B3xfkfpA4CwCQMJh45BKAf%2BICAYA3sihSB6QS0slKAsIzRChKBR9Vgd9SASdPk7AxlYD8BWkkgAg78n9SAqBk0HhoQuBjASh8gShN8x8uRgBiQ6BSADZMA/A7BVgqBSIwCsJICacwhQgJAjFChjAUIUJaAqJDoqJjAqJnRKBNU250BKAJA7kJDIBvZSAj9OBN96JR8JAfgKD6BqDaD6DGCO5mDKAqAABFXUCPNaaMKiPg/IFCZ0Q6fIYQ4wQoKifIGw2gSgfAakc%2BPAm/UfTSdgzATg7g3g/gwQ4Q0Q4FbHJESQzrUReAOQhQpQ%2BiUgtQpAC6ScK/BtCrLgZQRwGGVwdwTwR3BoIIEgSiSIaIbEOIBINAiIBwAmDze4SBKTW/QtdgOgFQifM/WA6/AaYQ5QGzeQRQPgb2Qg%2BsXAQgSqHYegPgQYG3c5OGCYjMLfHfVI/fQ/Y/TgU/afTorgW/e/UgR/OfVI8fTgfIDozA6/RYp/VIgmCiSEEAFCIAA%3D。注意我必须转换返回值calloc,因为 godbolt 使用 C++ 编译器,而不是 C 编译器。内循环来自.L3 to jne .L3. See https://stackoverflow.com/tags/x86/info https://stackoverflow.com/tags/x86/info用于 x86 asm 链接。也可以看看微融合和寻址模式 https://stackoverflow.com/questions/26046634/micro-fusion-and-addressing-modes,因为 Sandybridge 的这一更改尚未纳入 Agner Fog 的手册中。)。

表现:

$ perf stat -e task-clock,cycles,instructions,r1b1,r10e,stalled-cycles-frontend,stalled-cycles-backend,L1-dcache-load-misses,cache-misses ./fl3-vec 
CS201 - Asgmt 4 - I. Forgot
sum = 0; help=6000000000

 Performance counter stats for './fl3-vec':

       1086.571078      task-clock (msec)         #    1.000 CPUs utilized          
     4,072,679,849      cycles                    #    3.748 GHz                    
     2,629,419,883      instructions              #    0.65  insns per cycle        
                                                  #    1.27  stalled cycles per insn
     4,028,715,968      r1b1                      # 3707.733 M/sec  # unfused uops
     2,257,875,023      r10e                      # 2077.982 M/sec  # fused uops.  lower than insns because of macro-fusion
     3,328,275,626      stalled-cycles-frontend   #   81.72% frontend cycles idle   
     1,648,011,059      stalled-cycles-backend    #   40.47% backend  cycles idle   
       751,736,741      L1-dcache-load-misses     #  691.843 M/sec                  
            18,772      cache-misses              #    0.017 M/sec                  

       1.086925466 seconds time elapsed

我仍然不知道为什么每个周期的指令数如此之低。内部循环使用 4 个独立的累加器,我用 gdb 检查了指针是否对齐。所以缓存库冲突不应该是问题。 Sandybridge L2 缓存可以维持每个周期 1 个 32B 传输,这应该跟上每个周期 1 个 32B FP 向量加法。

从L1加载32B负载需要2个周期(直到Haswell,Intel才将32B加载变成单周期操作)。但是,有 2 个加载端口,因此每个周期的持续吞吐量为 32B(我们尚未达到)。

也许负载需要在使用之前进行管道传输,以最大程度地减少负载停止时 ROB(重新排序缓冲区)被填满的情况?但性能计数器表明 L1 缓存命中率相当高,因此从 L2 到 L1 的硬件预取似乎正在完成其工作。

每个周期 0.65 条指令仅使向量 FP 加法器饱和的一半左右。这令人沮丧。甚至IACA https://software.intel.com/en-us/articles/intel-architecture-code-analyzer表示循环每次迭代应运行 4 个周期。 (即使加载端口和端口 1(FP 加法器所在的位置)饱和):/

更新:我想 L2 延迟毕竟是问题所在。将 ARRAY_SIZE 减少到 1008(16 的倍数),并将 N_TIMES 增加 10 倍,使运行时间减少到 0.5 秒。即每个周期 1.68 个insns。 (内部循环总共有 7 条指令用于 4 个 FP 加法,因此我们最终使向量 FP 加法单元和加载端口饱和。)不知道为什么硬件预取器在一次停顿后无法领先,然后保持领先。软件预取可能有帮助吗?也许以某种方式避免让硬件预取器运行经过数组,而是再次开始预取数组的开头。 (循环平铺是一个更好的解决方案,请参见下文。)

Intel CPU 的 L1 数据和 L1 指令缓存各只有 32k。我认为你的阵列勉强适合 AMD CPU 上的 L1。

Gcc 通过将相同的值广播到并行添加中来进行矢量化的尝试似乎并不那么疯狂。如果它成功做到了这一点(使用多个累加器来隐藏延迟),那么它就可以仅用一半的内存带宽就使矢量 FP 加法器饱和。事实上,这几乎是一次洗礼,可能是因为广播的开销。

而且,这也很愚蠢。这N_TIMES只是一个重复的工作。我们实际上不想针对多次执行相同的工作进行优化。除非我们想在这样愚蠢的任务中获胜。执行此操作的源级方法是增加i在我们允许修改的部分代码中:

for (...) {
    sum += a[j] + a[j] + a[j] + a[j];
}
i += 3;  // The inner loop does 4 total iterations of the outer loop

更现实的是,要处理这个问题,您可以互换循环(循环数组一次,将每个值添加 N_TIMES 次)。我想我已经读到英特尔的编译器有时会为你做这件事。

更通用的技术称为缓存阻塞或循环平铺。这个想法是在适合缓存的小块中处理输入数据。根据您的算法,可以在一个块上执行各个阶段的操作,然后对下一个块重复,而不是让每个阶段循环整个输入。一如既往,一旦你知道了一个技巧的正确名称(并且它确实存在),你就可以在谷歌上搜索大量信息。

你可以通过规则律师的方式将一个交换循环放入一个if (i == 0)阻止您可以修改的代码部分。它仍然会执行相同数量的添加,但以更缓存优化的顺序进行。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C 中双精度数组的优化和[重复] 的相关文章

  • 如何使用C从http下载文件?

    最近几天我试图弄清楚如何从 URL 下载文件 这是我对套接字的第一个挑战 我用它来了解协议 所以我想在没有 cURL 库的情况下只用 C 语言来完成它 我搜索了很多 现在我可以打印页面的源代码 但我认为这与文件不同 我不必只将接收到的数据从
  • 如何使用T4从一个模板同时生成两个文件?

    我遇到的情况是 我需要生成两个 CSharp 代码文件 它们的代码几乎相同 但方法的输入和输出类型的命名空间不同 事实上 每个文件都针对特定国家 地区 并且类型来自特定国家 地区的 WSDL 我正在围绕服务编写一些包装器 逻辑完全相同 但从
  • CMake(Ninja 后端)使用 /MT 编译

    我有一个类似的问题CMake 使用 MT 而不是 MD 进行编译 https stackoverflow com questions 14172856 cmake compile with mt instead of md但有一些差异 我正
  • 大量互斥体对性能的影响

    假设我有一个包含 1 000 000 个元素的数组 以及多个工作线程 每个线程都操作该数组中的数据 工作线程可能会使用新数据更新已填充的元素 但每个操作仅限于单个数组元素 并且独立于任何其他元素的值 使用单个互斥锁来保护整个数组显然会导致高
  • 自己绘制的WPF自定义滑块

    这是我关于堆栈溢出的第一个问题 所以不要踢它 我在尝试创建 Mac 风格的滑块控件时遇到问题 我已经发现这个解决方案 http www codeproject com KB miscctrl MAC Slider aspx我已经在我的解决方
  • X 轴和 Z 轴上的 Quaternion.Slerp,无 Y 轴

    I am trying to rotate the Player about X Y and Z axis The Y axis should not move from last angle Example if I rotate 45
  • MINIX内部碎片2

    我正在用 C 语言编写一些软件 它递归地列出给定目录中的所有文件 现在我需要计算出内部碎片 我花了很长时间研究这个问题 发现 ext2 上的内部碎片只发生在最后一个块中 我知道理论上你应该能够从索引节点号获得第一个和最后一个块地址 但我不知
  • 如何在Windows窗体中打开进程

    我想在我的 Windows 窗体应用程序中打开进程 例如 我希望当用户按下 Windows 窗体容器之一中的按钮时 mstsc exe 将打开 如果他按下按钮 它将在另一个容器上打开 IE DllImport user32 dll SetL
  • 如何在VS2005中使用从.bat而不是.exe启动的外部程序进行调试?

    在我的 c 项目的调试属性中 我选择了 启动外部程序 并选择了我希望将调试器附加到的程序的 exe 但是 现在我需要从 bat 文件而不是 exe 启动程序 但 VS2005 似乎不允许这样做 这可能吗 编辑 为了澄清 我需要调试从 bat
  • 在 clang 中向量化函数

    我正在尝试根据此用 clang 对以下函数进行矢量化铿锵参考 http llvm org docs Vectorizers html 它采用字节数组向量并根据以下条件应用掩码this RFC https www rfc editor org
  • 从单应性估计 R/T

    我一直在尝试计算 2 个图像中的特征 然后将这些特征传递回CameraParams R没有运气 特征已成功计算并匹配 但是问题是将它们传递回R t 我明白你必须分解Homography为了使这一点成为可能 我已经使用如下方法完成了 http
  • 为什么我可以在另一个函数中定义一个函数?

    请参阅下面的代码 我在另一个函数中定义了一个函数 void test1 void void test2 void printf test2 n printf test1 n int main void test1 return 0 这个用法
  • c++ - <未解析的重载函数类型>

    在我的班级里叫Mat 我想要一个将另一个函数作为参数的函数 现在我有下面 4 个函数 但是在调用 print 时出现错误 第二行给了我一个错误 但我不明白为什么 因为第一行有效 唯一的区别是功能f不是班级成员Mat but f2是 失败的是
  • 如何防止 Lotus Notes 用户转发或复制通过 System.Net.Mail 发送的邮件?

    我想使用 SMTP 客户端 uiing microsft net 以 C 作为编程语言发送电子邮件 但是对于通过SMTP客户端发送的电子邮件 我们是否可以添加 禁止转发 或 禁止复制 等安全功能 我不希望电子邮件的收件人转发或复制电子邮件的
  • 在多线程环境中捕获信号

    我有一个大型程序 需要尽可能具有弹性 并且有大量线程 我需要捕获所有信号SIGBUS SIGSEGV 并在必要时重新初始化有问题的线程 或者禁用该线程以继续减少功能 我的第一个想法是做一个setjump 然后设置信号处理程序 可以记录问题
  • 具有行业级约束的 SciPy 投资组合优化

    尝试在这里优化投资组合权重分配 通过限制风险来最大化我的回报函数 我可以毫无问题地通过简单的约束 所有权重之和等于 1 找到产生我的回报函数的优化权重 并做出另一个约束 即我的总风险低于目标风险 我的问题是 如何为每个组添加行业权重界限 我
  • 异步/等待 - 是*并发*吗?

    我一直在考虑 C 5 中新的异步内容 并且出现了一个特殊问题 据我了解 await关键字是一个简洁的编译器技巧 语法糖来实现连续传递 http en wikipedia org wiki Continuation passing style
  • 尝试后终于没有被调用

    由于某种原因 在我的控制台应用程序中 我无法运行我的finally 块 我编写这段代码是为了测试finally块是如何工作的 所以它非常简单 static void Main int i 0 try int j 1 i Generate a
  • 使用通用存储库模式和流畅的 nHibernate

    我目前正在开发一个中型应用程序 它将访问不同站点上的 2 个或更多 SQL 数据库等 我正在考虑使用类似的东西 http mikehadlow blogspot com 2008 03 using irepository pattern w
  • 将同步 zip 操作转换为异步

    我们有一个现有的库 其中一些方法需要转换为异步方法 但是我不确定如何使用以下方法执行此操作 错误处理已被删除 该方法的目的是压缩文件并将其保存到磁盘 请注意 zip 类不公开任何异步方法 public static bool ZipAndS

随机推荐

  • 尝试使用 VideoCapture 和 imshow(),在 cv::imshow 中引发断言失败 (size.width>0 && size.height>0)

    我正在使用带有 OpenCV 的 Visual Studio Express 20132 4 7 按照此tutorial http www youtube com watch v cgo0UitHfp8 我花了几个小时在网上搜索解决方案 包
  • 将两个表中的两列合并(合并)到一个 SQL 查询中

    我有以下两个表 您也可以在SQL fiddle here http www sqlfiddle com 9 70dba18 9 Sent Orders CREATE TABLE Send Orders Send Date DATE Prod
  • 是否可以在 2 个 iOS 设备之间建立套接字连接

    是否可以在连接到同一网络 无网络 的 2 个 iOS 设备之间建立套接字连接 如果可能的话 CocoaAsyncSocket 项目 对我有用吗 我只想发一条消息Device A to 将应用程序置于后台的设备 B 什么时候Device B收
  • 将 FILE 指针传递给函数

    我对此有点困惑 不太确定 我想做的是将文件名传递给terminal cmd将被打开并读取 myfunction char fileName FILE readFile if readFile fopen fileName r NULL re
  • bash - 从本地计算机运行远程脚本

    我试过这个 bin bash ssh email protected cdn cgi l email protection sudo etc init d script restart 但我收到这个错误 sudo no tty presen
  • NVDA开启时区分Mozilla中的按键和点击

    要求是区分Mozilla浏览器中的按键和鼠标点击事件 条件是 Mozilla 浏览器应该能够区分事件 点击和进入 NVDA 已开启 在您的 MouseEvent 上 检查detail https developer mozilla org
  • Zend OAuth 与 Twitter 单一访问令牌

    我正在开发一个应用程序 它要求用户使用 twitter 和 OAuth 登录 感谢 Zend OAuth 一切都工作得很好 问题是 Web 应用程序还会对 twitter API 进行一些调用 但这些调用将在内部处理 无需进行身份验证 Tw
  • 对基类的派生类进行序列化和反序列化

    我正在尝试创建一个基类 我可以从中继承 向派生类添加属性 并使用基类中的 Load 和 Save 方法 我发现自己一遍又一遍地编写 加载 和 保存 并且我想对其应用一些 DRY namespace Common using System u
  • Razor 智能感知错误:无法使用功能“扩展方法”,因为它不是 ISO-2 C# 语言规范的一部分

    Goal 使用cshtmlRazor用于格式化数据的模板 将 cshtml Razor 模板嵌入类库中 如下所示Embedded Resources Use Linqcshtml模板中的语句和扩展方法 我创建了一个新的类库项目 然后进行了调
  • Rails 应用程序的 SaaS 计费:Chargify、PayPal 还是...?

    我正在大二学习一般编程 更具体地说是 Ruby on Rails 我创建了多个应用程序 最后终于有了一个我想开始收费的应用程序 我以前从未实现过这样的事情 而且我觉得 从我读到的内容来看 提供的大多数文档都有点超出我的理解范围 我不介意深入
  • 处理历史日历日期

    处理旧日历形式中描述的历史日期有哪些标准和策略 当代的公历 http en wikipedia org wiki Gregorian calendar使用内置编程语言库或数据格式 例如 不同长度的月份 闰年等 相对容易处理日历ISO8601
  • 具有跨域 iframe 的页面的 Greasemonkey 脚本

    我想实现 JavaScript 来修改输入字段的内容iframe从另一个域加载 这是网站 http www ah nl over ah services mobiel online opwaarderen 困难 不知何故 jQuery 加载
  • GitHub 上的“密钥无效”消息

    我已根据概述的过程为新服务器安装生成了 SSH 密钥here http help github com mac set up git 但是 当我复制内容时id rsa pub在 GitHub 上的密钥列表中 我收到错误消息 密钥无效 请确保
  • HTML 中的 H1-H6 字体大小

    在 HTML 中 我想也是在一般的排版中 H1 H6 元素似乎有一些定义的大小 即 如果基线字体大小为 16px 或 100 则 h1 w c 应为 2 25em 36px H2 w c 应该是 1 5em 24px 等等 这些变量从哪里来
  • 如何在 Python 中将 XML 转换为 JSON? [复制]

    这个问题在这里已经有答案了 可能的重复 使用 Python 将 XML 转换为 JSON https stackoverflow com questions 191536 converting xml to json using pytho
  • 如何关闭 PrimeFaces 套接字连接

    我们在 J2E 应用程序中使用 PrimeFaces 4 0 套接字 和atmosphere 2 0 3 进行服务器端推送 应用程序的问题是在关闭浏览器或从应用程序注销后不会关闭套接字 因此 应用程序生成一个处于 CLOSE WAIT 状态
  • 询问SPARQL资源是否存在

    检查 SPARQL 资源是否存在的好方法是什么 我正在寻找相当于向例如发送 HTTP GET 请求的方法 http dbpedia org resource Game of Thrones并检查 HTTP 状态代码 但我想使用 SPARQL
  • Angular 2 更新 [已禁用]

    我正在尝试根据另一个选择的值 真 假 动态启用 禁用一组选择输入 然而 它似乎只适用于初始页面加载 表单加载时禁用选择输入 当我将控制输入更改为true 其他输入现已启用 但是 在初始启用后它们不会变回原样 我的代码如下 tr td pro
  • App Engine SDK PIL 错误

    我正在 MacOS 上为 Google App Engine 开发 Python 应用程序 但在尝试设置 PIL 进行本地开发时遇到了麻烦 我在 virtualenv 中运行 Python 2 5 并且还使用 pip 在 vi rtuale
  • C 中双精度数组的优化和[重复]

    这个问题在这里已经有答案了 我有一项任务 我必须完成一个程序并使其在时间上更有效率 原来的代码是 include