Вызов инструкции fsincos в LLVM медленнее, чем вызов функций libc sin/cos?

Я работаю над языком, который компилируется с LLVM. Ради интереса я хотел сделать несколько микробенчмарков. В одном из них я запускаю несколько миллионов вычислений sin / cos в цикле. В псевдокоде это выглядит так:

var x: Double = 0.0
for (i <- 0 to 100 000 000)
  x = sin(x)^2 + cos(x)^2
return x.toInteger

Если я вычисляю sin / cos, используя встроенную сборку LLVM IR в форме:

%sc = call { double, double } asm "fsincos", "={st(1)},={st},1,~{dirflag},~{fpsr},~{flags}" (double %"res") nounwind

это быстрее, чем использовать fsin и fcos отдельно вместо fsincos. Тем не менее, это медленнее, чем если бы я звонил llvm.sin.f64 а также llvm.cos.f64 intrinsics отдельно, которые компилируются для вызовов функций C math lib, по крайней мере с целевыми настройками, которые я использую (x86_64 с включенным SSE).

Кажется, LLVM вставляет некоторые преобразования между FP одинарной / двойной точности - это может быть причиной. Это почему? Извините, я относительный новичок на сборке:

    .globl  main
    .align  16, 0x90
    .type   main,@function
main:                                   # @main
    .cfi_startproc
# BB#0:                                 # %loopEntry1
    xorps   %xmm0, %xmm0
    movl    $-1, %eax
    jmp     .LBB44_1
    .align  16, 0x90
.LBB44_2:                               # %then4
                                    #   in Loop: Header=BB44_1 Depth=1
    movss   %xmm0, -4(%rsp)
    flds    -4(%rsp)
    #APP
    fsincos
    #NO_APP
    fstpl   -16(%rsp)
    fstpl   -24(%rsp)
    movsd   -16(%rsp), %xmm0
    mulsd   %xmm0, %xmm0
    cvtsd2ss        %xmm0, %xmm1
    movsd   -24(%rsp), %xmm0
    mulsd   %xmm0, %xmm0
    cvtsd2ss        %xmm0, %xmm0
    addss   %xmm1, %xmm0
.LBB44_1:                               # %loop2
                                    # =>This Inner Loop Header: Depth=1
    incl    %eax
    cmpl    $99999999, %eax         # imm = 0x5F5E0FF
    jle     .LBB44_2
# BB#3:                                 # %break3
    cvttss2si       %xmm0, %eax
    ret
.Ltmp160:
    .size   main, .Ltmp160-main
    .cfi_endproc

Тот же тест с вызовами llvm sin / cos intrinsics:

    .globl  main
    .align  16, 0x90
    .type   main,@function
main:                                   # @main
    .cfi_startproc
# BB#0:                                 # %loopEntry1
    pushq   %rbx
.Ltmp162:
    .cfi_def_cfa_offset 16
    subq    $16, %rsp
.Ltmp163:
    .cfi_def_cfa_offset 32
.Ltmp164:
    .cfi_offset %rbx, -16
    xorps   %xmm0, %xmm0
    movl    $-1, %ebx
    jmp     .LBB44_1
    .align  16, 0x90
.LBB44_2:                               # %then4
                                    #   in Loop: Header=BB44_1 Depth=1
    movsd   %xmm0, (%rsp)           # 8-byte Spill
    callq   cos
    mulsd   %xmm0, %xmm0
    movsd   %xmm0, 8(%rsp)          # 8-byte Spill
    movsd   (%rsp), %xmm0           # 8-byte Reload
    callq   sin
    mulsd   %xmm0, %xmm0
    addsd   8(%rsp), %xmm0          # 8-byte Folded Reload
.LBB44_1:                               # %loop2
                                    # =>This Inner Loop Header: Depth=1
    incl    %ebx
    cmpl    $99999999, %ebx         # imm = 0x5F5E0FF
    jle     .LBB44_2
# BB#3:                                 # %break3
    cvttsd2si       %xmm0, %eax
    addq    $16, %rsp
    popq    %rbx
    ret
.Ltmp165:
    .size   main, .Ltmp165-main
    .cfi_endproc

Можете ли вы предложить, как будет выглядеть идеальная сборка с fsincos? PS. Добавление -enable-unsafe-fp-math к llc приводит к исчезновению конверсий и переключению на удвоения (fldl и т. Д.), Но скорость остается неизменной.

    .globl  main
    .align  16, 0x90
    .type   main,@function
main:                                   # @main
    .cfi_startproc
# BB#0:                                 # %loopEntry1
    xorps   %xmm0, %xmm0
    movl    $-1, %eax
    jmp     .LBB44_1
    .align  16, 0x90
.LBB44_2:                               # %then4
                                    #   in Loop: Header=BB44_1 Depth=1
    movsd   %xmm0, -8(%rsp)
    fldl    -8(%rsp)
    #APP
    fsincos
    #NO_APP
    fstpl   -24(%rsp)
    fstpl   -16(%rsp)
    movsd   -24(%rsp), %xmm1
    mulsd   %xmm1, %xmm1
    movsd   -16(%rsp), %xmm0
    mulsd   %xmm0, %xmm0
    addsd   %xmm1, %xmm0
.LBB44_1:                               # %loop2
                                    # =>This Inner Loop Header: Depth=1
    incl    %eax
    cmpl    $99999999, %eax         # imm = 0x5F5E0FF
    jle     .LBB44_2
# BB#3:                                 # %break3
    cvttsd2si       %xmm0, %eax
    ret
.Ltmp160:
    .size   main, .Ltmp160-main
    .cfi_endproc

2 ответа

Аппаратная синхронизация идет медленно.

Слишком много документов утверждают, что инструкции x87, такие как fsin или же fsincos являются самым быстрым способом выполнения тригонометрических функций. Эти претензии часто ошибочны.

Самый быстрый способ зависит от вашего процессора. Поскольку процессоры становятся быстрее, старые инструкции по запуску оборудования, такие как fsin не успевал. В некоторых процессорах программная функция, использующая полиномиальное приближение для синуса или другой функции триггера, теперь быстрее, чем аппаратная инструкция.

Короче, fsincos слишком медленно

Аппаратный триг устарел.

Существует достаточно доказательств того, что платформа x86-64 отошла от аппаратного триггера.

  • amd64 предпочитает SSE вместо x87 для поплавков. Тем не менее, SSE не имеет эквивалентов для команд x87, таких как fsin,
  • Для amd64 libm как во FreeBSD, так и в glibc реализует sin () и такие функции в программном обеспечении, а не с триггером x87. glibc оптимизировал сборку x86-64 для sinf () (синус одинарной точности) с полиномиальным приближением, а не с x87 fsin, NetBSD и OpenBSD сделали противоположный выбор: их libm для amd64 использует инструкции x87.
  • Steel Bank Common Lisp использует fsin в бэкэнде x86, но не в бэкенде x86-64. Для x86-64 SBCL компилирует код, который вызывает sin () в libm.

Аппаратный триггер проигрывает гонку.

Я рассчитал аппаратное и программное обеспечение на AMD Phenom II X2 560 (3,3 ГГц) с 2010 года. Я написал программу на C с таким циклом:

volatile double a, s;
/* ... */
for (i = 0; i < 100000000; i++)
        s = sin(a);

Я скомпилировал эту программу дважды, с двумя различными реализациями sin (). Жесткий грех () использует x87 fsin, Мягкий грех () использует полиномиальное приближение. Мой компилятор C, gcc -O2, не заменил мой вызов sin () на встроенный fsin,

Вот результаты для греха (0.5):

$ time race-hard 0.5
    0m3.40s real     0m3.40s user     0m0.00s system
$ time race-soft 0.5
    0m1.13s real     0m1.15s user     0m0.00s system

Здесь soft sin(0.5) настолько быстр, что этот процессор будет делать soft sin(0.5) и soft cos(0.5) быстрее, чем один x87 fsin,

И за грех (123):

$ time race-hard 123
    0m3.61s real     0m3.62s user     0m0.00s system
$ time race-soft 123
    0m3.08s real     0m3.07s user     0m0.01s system

Мягкий грех (123) медленнее мягкого греха (0,5), потому что 123 слишком много для полинома, поэтому функция должна вычесть несколько кратных 2π. Если я тоже хочу cos(123), есть вероятность, что x87 fsincos будет быстрее, чем soft sin(123) и soft cos(123), для этого процессора с 2010 года.

fsincos— это инструкция FPU x87, которая работает с числами с плавающей запятой 80-битной точности. Он не поддерживает автовекторизацию, но обеспечивает гораздо более высокую точность, чем 64-битные инструкции.

sinиcosоперируйте инструкциями с 64-битной точностью, поэтому даже более низкая точность сделает их быстрее. Код, который выполняется на FPU (80-битный тип), никогда не будет автоматически векторизован, поскольку он не поддерживается, а обычный 64-битный код (доdoubletype) будет, так что это может сделать его в несколько раз быстрее с SSE/AVX/NEON и т. д.

FPU следует использовать только тогда, когда вам действительно нужна 80-битная точность. Сказать, что оно устарело, не совсем верно. Он устарел только в 99% случаев и все еще нужен в 1% случаев.

Чтобы увидетьfsinиfcosгенерируется с помощью компилятораlong doubleтип (80-битный с плавающей запятой) сsinl coslфункции.

Другие вопросы по тегам