【问题标题】:Why sin/cos are slower when optimizations are enabled?为什么启用优化后 sin/cos 会变慢?
【发布时间】:2011-10-22 02:23:59
【问题描述】:

看了一个与sin/cos的性能相关的问题(Why is std::sin() and std::cos() slower than sin() and cos()?),我用他的代码做了一些测试,发现了一个奇怪的东西:如果我用float值调用sin/cos,它比使用优化编译时使用 double。

#include <cmath>
#include <cstdio>

const int N = 4000;

float cosine[N][N];
float sine[N][N];

int main() {
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            float ang = i*j*2*M_PI/N;
            cosine[i][j] = cos(ang);
            sine[i][j] = sin(ang);
        }
    }
}

通过上面的代码我得到:

使用 -O0:2.402 秒

使用 -O1:9.004 秒

使用 -O2:9.013 秒

使用 -O3:9.001 秒

如果我改变了

float ang = i*j*2*M_PI/N;

double ang = i*j*2*M_PI/N;

我明白了:

使用-O0:2.362s

使用 -O1:1.188 秒

使用 -O2:1.197 秒

使用 -O3:1.197 秒

如果不进行优化,第一次测试怎么能这么快?

我使用的是 g++ (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2,64 位。

编辑:更改了标题以更好地描述问题。

编辑:添加汇编代码

使用 O0 进行第一次测试的组装:

    .file   "main.cpp"
.globl cosine
    .bss
    .align 32
    .type   cosine, @object
    .size   cosine, 64000000
cosine:
    .zero   64000000
.globl sine
    .align 32
    .type   sine, @object
    .size   sine, 64000000
sine:
    .zero   64000000
    .text
.globl main
    .type   main, @function
main:
.LFB87:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    movq    %rsp, %rbp
    .cfi_offset 6, -16
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    $0, -4(%rbp)
    jmp .L2
.L5:
    movl    $0, -8(%rbp)
    jmp .L3
.L4:
    movl    -4(%rbp), %eax
    imull   -8(%rbp), %eax
    addl    %eax, %eax
    cvtsi2sd    %eax, %xmm0
    movsd   .LC0(%rip), %xmm1
    mulsd   %xmm1, %xmm0
    movsd   .LC1(%rip), %xmm1
    divsd   %xmm1, %xmm0
    unpcklpd    %xmm0, %xmm0
    cvtpd2ps    %xmm0, %xmm0
    movss   %xmm0, -12(%rbp)
    movss   -12(%rbp), %xmm0
    cvtps2pd    %xmm0, %xmm0
    call    cos
    unpcklpd    %xmm0, %xmm0
    cvtpd2ps    %xmm0, %xmm0
    movl    -8(%rbp), %eax
    cltq
    movl    -4(%rbp), %edx
    movslq  %edx, %rdx
    imulq   $4000, %rdx, %rdx
    leaq    (%rdx,%rax), %rax
    movss   %xmm0, cosine(,%rax,4)
    movss   -12(%rbp), %xmm0
    cvtps2pd    %xmm0, %xmm0
    call    sin
    unpcklpd    %xmm0, %xmm0
    cvtpd2ps    %xmm0, %xmm0
    movl    -8(%rbp), %eax
    cltq
    movl    -4(%rbp), %edx
    movslq  %edx, %rdx
    imulq   $4000, %rdx, %rdx
    leaq    (%rdx,%rax), %rax
    movss   %xmm0, sine(,%rax,4)
    addl    $1, -8(%rbp)
.L3:
    cmpl    $3999, -8(%rbp)
    setle   %al
    testb   %al, %al
    jne .L4
    addl    $1, -4(%rbp)
.L2:
    cmpl    $3999, -4(%rbp)
    setle   %al
    testb   %al, %al
    jne .L5
    movl    $0, %eax
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE87:
    .size   main, .-main
    .section    .rodata
    .align 4
    .type   _ZL1N, @object
    .size   _ZL1N, 4
_ZL1N:
    .long   4000
    .align 8
.LC0:
    .long   1413754136
    .long   1074340347
    .align 8
.LC1:
    .long   0
    .long   1085227008
    .ident  "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"
    .section    .note.GNU-stack,"",@progbits

使用 O3 进行第一次测试的组装:

    .file   "main.cpp"
    .text
    .p2align 4,,15
.globl main
    .type   main, @function
main:
.LFB121:
    .cfi_startproc
    pushq   %r15
    .cfi_def_cfa_offset 16
    xorl    %r15d, %r15d
    .cfi_offset 15, -16
    pushq   %r14
    .cfi_def_cfa_offset 24
    movl    $cosine+16000, %r14d
    .cfi_offset 14, -24
    pushq   %r13
    .cfi_def_cfa_offset 32
    xorl    %r13d, %r13d
    .cfi_offset 13, -32
    pushq   %r12
    .cfi_def_cfa_offset 40
    pushq   %rbp
    .cfi_def_cfa_offset 48
    pushq   %rbx
    .cfi_def_cfa_offset 56
    subq    $24, %rsp
    .cfi_def_cfa_offset 80
    .p2align 4,,10
    .p2align 3
.L2:
    movslq  %r15d, %rbp
    .cfi_offset 3, -56
    .cfi_offset 6, -48
    .cfi_offset 12, -40
    movl    %r13d, %r12d
    movl    $0x3f800000, %edx
    imulq   $16000, %rbp, %rbp
    xorl    %eax, %eax
    leaq    cosine(%rbp), %rbx
    addq    $sine, %rbp
    jmp .L5
    .p2align 4,,10
    .p2align 3
.L3:
    movl    %r12d, %eax
    leaq    8(%rsp), %rsi
    leaq    12(%rsp), %rdi
    subl    %r13d, %eax
    cvtsi2sd    %eax, %xmm0
    mulsd   .LC2(%rip), %xmm0
    divsd   .LC3(%rip), %xmm0
    unpcklpd    %xmm0, %xmm0
    cvtpd2ps    %xmm0, %xmm0
    call    sincosf
    movl    8(%rsp), %edx
    movl    12(%rsp), %eax
.L5:
    movl    %edx, (%rbx)
    addq    $4, %rbx
    movl    %eax, 0(%rbp)
    addl    %r13d, %r12d
    addq    $4, %rbp
    cmpq    %r14, %rbx
    jne .L3
    addl    $1, %r15d
    addl    $2, %r13d
    leaq    16000(%rbx), %r14
    cmpl    $4000, %r15d
    jne .L2
    addq    $24, %rsp
    .cfi_def_cfa_offset 56
    xorl    %eax, %eax
    popq    %rbx
    .cfi_def_cfa_offset 48
    popq    %rbp
    .cfi_def_cfa_offset 40
    popq    %r12
    .cfi_def_cfa_offset 32
    popq    %r13
    .cfi_def_cfa_offset 24
    popq    %r14
    .cfi_def_cfa_offset 16
    popq    %r15
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc
.LFE121:
    .size   main, .-main
.globl cosine
    .bss
    .align 32
    .type   cosine, @object
    .size   cosine, 64000000
cosine:
    .zero   64000000
.globl sine
    .align 32
    .type   sine, @object
    .size   sine, 64000000
sine:
    .zero   64000000
    .section    .rodata.cst8,"aM",@progbits,8
    .align 8
.LC2:
    .long   1413754136
    .long   1074340347
    .align 8
.LC3:
    .long   0
    .long   1085227008
    .ident  "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"
    .section    .note.GNU-stack,"",@progbits

【问题讨论】:

  • 如果您为每种情况提供了生成的汇编代码,您的问题会更容易回答。使用 gcc 的 -S 选项来创建程序集列表。
  • @Greg Hewgill:向问题添加了汇编代码。
  • @fbafelipe:很明显,编译器将xmm1 与-O0 一起使用,而不是与-O3 一起使用。不过,我不知道为什么......
  • 我今天用你的小基准代码和试验函数 ::sin、::sinf、std::sin、sincos、sincosf 得到了令人惊讶的结果。取决于处理器(ARM64 与 AMD64)和编译器标志(-O0 与 -O3,-ffast-math 与否),可比较版本之间的运行时间差异很大(因子 2 到 5)。简短的结论:测量!

标签: c++ performance


【解决方案1】:

这是一种可能性:

在 C 中,cos 是双精度,cosf 是单精度。在 C++ 中,std::cos 对双精度和单精度都有重载。

你没有打电话给std::cos。如果&lt;cmath&gt; 也没有重载::cos(据我所知,它不是必需的),那么您只是在调用C 双精度函数。如果是这种情况,那么您将承受在 float、double 和 back 之间转换的成本。

现在,一些标准库将cos(float x) 实现为(float)cos((double)x),因此即使您调用float 函数,它也可能仍在幕后进行转换。

不过,这不应解释 9 倍的性能差异。

【讨论】:

  • 我将调用更改为 cosf 和 sinf,O0 变为 17.198s,而 O3 则相同(8.999s)。检查我在问题中发布的程序集,它显示了对 sincosf 的调用(而不是 sincos - 请注意,它使用了一个同时计算 sin 和 cos 的函数)。因此,似乎通过优化,编译器决定更改为较慢的函数而不是进行强制转换...
  • gnu.org/s/hello/manual/libc/FP-Function-Optimizations.html - 使用 __NO_MATH_INLINES 会消除这种行为吗?
  • @andrew Cooke:不,不管有没有 __NO_MATH_INLINES,结果都一样。
  • floatdouble 之间的转换不考虑它。我今天用 g++ 进行了一些测试,发现使用 -O2 时,float 代码要慢得多。但是,当我使用手动转换进行测试时,如下所示:(float)sin((double)input) 我发现优化的float 代码比优化的double 代码运行更快,即使我强制使用float使用doublesin函数的代码。
【解决方案2】:

AFAIK 这是因为计算机本身以双精度工作。使用浮点数需要转换。'

【讨论】:

  • 但是为什么使用 -O0 比使用 -O3 更快?此性能问题仅在启用优化时发生。
  • @fbafelipe:优化尺寸?这将是最有意义的。通常大小是有代价的。
  • @fbafelipe:回答这个问题的唯一方法是反汇编编译后的程序,无论是否经过优化,就像您链接到的问题中所做的那样。
  • @Ethan:但是-Os是为了尺寸优化; -O3 应该以大小为代价提高速度,(例如)内联。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多