2016-04-04 2 views
4

Главный вопросFaster грех() для x64

ли кто-нибудь есть быстрый грех() реализацию для x64? Не обязательно быть чистым паскалем.

Объяснение

У меня есть приложение VCL, что в некоторых ситуациях работает намного медленнее, когда он скомпилирован для x64.

Он выполняет множество вычислений 3d с плавающей запятой, и я проследил это до того, что System.Sin() и System.Cos() намного медленнее на x64, когда входные значения становятся большими.

Я засек, создав простое приложение тест, который измеряет, сколько времени требуется, чтобы вычислить sin(x) с различными значениями х, и различия ОГРОМНЫ:

   call:  x64:  x86: 
       Sin(1) 16 ms 20 ms 
      Sin(10) 30 ms 20 ms 
      Sin(100) 32 ms 20 ms 
      Sin(1000) 34 ms 21 ms 
      Sin(10000) 30 ms 21 ms 
     Sin(100000) 30 ms 16 ms 
     Sin(1000000) 35 ms 20 ms 
     Sin(10000000) 581 ms 20 ms 
     Sin(100000000) 1026 ms 21 ms 
    Sin(1000000000) 1187 ms 22 ms 
    Sin(10000000000) 1320 ms 21 ms 
    Sin(100000000000) 1456 ms 20 ms 
    Sin(1000000000000) 1581 ms 17 ms 
Sin(10000000000000) 1717 ms 22 ms 
Sin(100000000000000) 1846 ms 23 ms 
      Sin(1E15) 1981 ms 21 ms 
      Sin(1E16) 2100 ms 21 ms 
      Sin(1E17) 2240 ms 22 ms 
      Sin(1E18) 2372 ms 18 ms 
       etc etc  etc 

То, что вы здесь видите, что sin(1E5) работает примерно в 300 раз быстрее, чем sin(1E8).

В случае, если вы заинтересованы, я создал таблицу выше, как это:

{$APPTYPE CONSOLE} 
program SinTest; 

uses Diagnostics, Math, SysUtils; 

var 
    i : Integer; 
    x : double; 
    sw: TStopwatch; 

begin 
    x := 1; 

    while X < 1E18 do 
    begin 
    sw := TStopwatch.StartNew; 
    for i := 1 to 500000 do 
     System.Sin(x); 

    // WriteLn(System.sin(x), #9,System.Sin(fmod(x,2*pi))); 

    sw.Stop; 

    WriteLn(' ', ('Sin(' + round(x).ToString + ')'):20, ' ', sw.ElapsedMilliseconds,' ms'); 

    x := x * 10; 
    end; 

    WriteLn('Press any key to continue'); 
    readln; 
end. 

Примечания:

  • Есть некоторые вопросы по StackOverflow относительно быстрее синусоидальные функции, но ни один из них есть исходный код, который полезен для порта Delphi, как этот: Fastest implementation of sine, cosine and square root in C++ (doesn't need to be much accurate)

  • Остальная часть x64 работает быстрее, чем 32 бит nterpart

  • Я нашел немного дрянной обходной путь, сделав это: Sin(FMod(x,2*pi)). Он дает правильные результаты, и он работает быстро для больших чисел. Для меньших чисел это, конечно, немного медленнее.

+2

Предположительно, вас не волнует точность или вы не будете называть триггерные функции такими большими значениями. Неужели вы понимаете, что округление означает, что триггерные функции бессмысленны для таких входных значений? Или точность просто не важна для вас? –

+1

Итак, посмотрите, можете ли вы угадать вывод этой программы: '{$ APPTYPE CONSOLE} var s1, s2: Single; начало s1: = 10000000.5; s2: = 10000000,0; Writeln (s1 = s2); конец. 'Вот ключ. Выход не является FALSE. –

+1

Кажется, что MSVC может сделать это быстрее, и мне было бы интересно узнать, как, потому что я уверен, что он делает это быстрее для входных значений, которые тоже разумны. Но для ваших больших входных значений вы тратите свое время даже на вызов этих триггерных функций, как показывает мой предыдущий комментарий. –

ответ

3

Хотя это, вероятно, будет довольно сильно обескуражен в коде пользовательского режима (и полностью запрещено в коде режима ядра), если вы сделать хотите сохранить поведение x87 унаследованную в вашем x64 код, который вы МОГ написать такую ​​функцию:

function SinX87(x:double):double; 
var 
    d : double; 
asm 
    movsd qword ptr [rbp+8], xmm0 
    fld qword ptr [rbp+8] 
    fsin 
    fstp qword ptr [rbp+8] 
    movsd xmm0, qword ptr [rbp+8] 
end; 

Это добавляет немного накладные расходы, так как вы должны совать значение из ГСПА регистра в стек, загрузите его в блок x87, peform расчет, вытолкнуть значение обратно в стек, а затем загрузить его обратно в XMM0 для функции результат. Расчет sin довольно тяжелый, так что это относительно небольшие накладные расходы. Я бы действительно сделал это, только если вам нужно было сохранить whatever idiosyncracies реализации x87's sin.

Существуют и другие библиотеки, которые вычисляют sin более эффективно в коде x64, чем подпрограммы Delphi purepascal. Мое подавляющее предпочтение здесь состояло в том, чтобы экспортировать хороший набор C++-подпрограмм в DLL. Кроме того, как сказал Дэвид, использование триггерных функций со смехотворно большими аргументами в действительности не является разумной вещью.

+0

Прохладный, скорость очень стабильная, независимо от того, какой вход он получает. Для значений меньше pi это немного медленнее; остальное всегда быстрее. Результаты немного отличаются от Delphi System.Sin(), но это незначительно для чисел, с которыми мне нужно работать. Результаты выглядят хорошо. Это именно то, что мне нужно. Теперь все, что мне нужно сделать, это добавить некоторые уродливые вещи {$ ifdef}, а производительность под x64 будет восстановлена. Благодаря! –

+0

@WoutervanNifterick Кроме того, я не уверен, как будут обрабатываться исключения ... Я определенно проверил бы это в первую очередь. Не уверен, что управляющее слово x87 будет настроено на любое разумное значение по умолчанию в режиме x64 либо - я быстро его сбил, но есть предостережения, о которых следует помнить. –

+0

Протестировано, и в самом деле это немного отличается от вещей. Например, 'SinX87 (NaN)' не будет создавать никаких исключений, как это делает System.Sin(). Так что есть различия, но это большая помощь. Я сделаю некоторые дополнительные тесты, но до сих пор это выглядит так, как будто все делает именно так, как мне нужно. –

2

В случае, если вы заинтересованы в моем окончательном решении:

Я экспериментировал немного, делая это (как LU RD и е). - Джерри Гроб предложил):

function sin(x:double):double; 
begin 
    if x<1E6 then 
    Result := system.sin(x) 
    else 
    Result := system.sin(fmod(x,2*pi)); 
end; 

Может быть, это что-то делать с предсказуемостью тестового кода на моем конкретном процессоре, но меньшие значения были фактически рассчитаны быстрее, если бы не сделать if, и просто всегда используйте fmod(). Странно, потому что нужно провести какое-то деление, которое я ожидаю замедлить, чем сравнивать два значения.

Так это то, что я в конечном итоге использую сейчас:

function sin(const x: double): double; { inline; } 
begin 
    {$IFDEF CPUX64} 
    Result := System.sin(Math.FMod(x,2*pi)); 
    {$ELSE} 
    Result := System.sin(x); 
    {$ENDIF} 
end; 

Кстати добавив inline, он побежал в 1,5 раза быстрее, даже. Затем он работает точно так же быстро, как функция J ... на моей машине. Но даже без Inline это уже в сотни раз быстрее, чем System.Sin(), поэтому я собираюсь для этого.

+1

Даже если вы используете 'fmod (x, 2 * pi)', как указал @DavidHeffernan, вы столкнулись с тем, что 'x', как переменная двойной точности, не может содержать более 17 десятичных цифр информации, поэтому вы теряете всю свою точность того, что передается в функцию 'sin'. Пример: если вы переходите 'x' от 100000000000000000.0 до 100000000000000000.1, представляя шаг .1-radian, эти два числа одинаковы, потому что когда добавляется .1, он теряется, потому что переменная двойной точности недостаточно широка для держите все это. Вы должны найти другой способ кодирования 'x'. –