2008-09-15 2 views
10

Мне нужно рассчитать Math.exp() из java очень часто, можно ли получить родную версию для работы быстрее, чем java's Math.exp() ??быстрее Math.exp() через JNI?

Я пробовал только jni + C, но это медленнее, чем просто java.

+0

Вы провели какое-либо тестирование производительности, чтобы получить точное количество времени, которое требуется Math.exp() по сравнению с версией JNI? Как насчет того, чтобы называться 10K раз, чтобы увидеть эффект JIT? – martinatime 2008-09-15 20:36:37

+0

Это зависит от вашего JVM, но обычно `Math.exp` * реализуется в C *. Однако вы можете использовать более быстрый (менее точный) алгоритм. – Joni 2012-02-17 08:01:13

ответ

11

+1 к написанию реализации собственного ехр(). То есть, если это действительно бутылочное горло в вашем приложении. Если вы можете иметь дело с небольшой неточностями, существует ряд чрезвычайно эффективных алгоритмов оценки экспонентов, некоторые из которых датируются веками. Насколько я понимаю, реализация exp() Java довольно медленная, даже для алгоритмов, которые должны возвращать «точные» результаты.

О, и не бойтесь писать, что реализация exp() в чистой-Java. JNI имеет много накладных расходов, и JVM может оптимизировать байт-код во время выполнения, иногда даже за пределами того, что C/C++ может достичь.

+0

Два важных момента здесь: (1) накладные расходы JNI часто перевешивают все другие соображения; (2) JVM JIT на удивление хороша (иногда равна или быстрее, чем C/C++) при оптимизации небольших методов, если машина «нагрета» достаточно. – kevinarpe 2016-03-02 09:55:45

6

Использование Java.

Кроме того, результаты кэша exp и затем вы можете найти ответ быстрее, чем вычислять их снова.

+0

Как вы можете кэшировать результаты? Кэширование может быть довольно дорогостоящим: я попытался использовать HashMap, и он был в два раза медленнее, чем просто вычисление exp. В моем тесте я вычисляю 71M exp, но с «только» 1.8M различными аргументами. – 2015-10-08 07:29:26

5

Вы хотите обернуть любой вызов цикла Math.exp() в C. В противном случае накладные расходы на маршаллинг между Java и C будут перегружать любое преимущество производительности.

0

Поскольку код Java будет скомпилирован в собственный код с компилятором точно в срок (JIT), нет причин использовать JNI для вызова собственного кода.

Кроме того, вы не должны кэшировать результаты метода, в котором входные параметры являются действительными числами с плавающей запятой. Полученные во времени успехи будут очень сильно потеряны в объеме используемого пространства.

0

Проблема с использованием JNI - это накладные расходы, связанные с вызовом JNI. В наши дни виртуальная машина Java довольно оптимизирована, и вызовы во встроенную Math.exp() автоматически оптимизированы для прямого вызова функции C exp(), и они могут даже оптимизироваться на прямую сборку с плавающей точкой x87 инструкции.

2

Реальный вопрос: у вас это бутылочная шее для вас? Профилировали ли вы свое приложение и обнаружили, что это основная причина замедления?

Если нет, я бы рекомендовал использовать версию Java. Старайтесь не предварительно оптимизировать, так как это приведет к замедлению развития. Вы можете потратить много времени на проблему, которая не может быть проблемой.

Это, как говорится, я думаю, что ваш тест дал вам свой ответ. Если jni + C работает медленнее, используйте версию java.

3

Возможно, вам удастся запустить его быстрее, если вы делаете это партиями. Создание JNI-кода добавляет накладные расходы, поэтому вы не хотите делать это для каждого exp(), который вам нужно рассчитать. Я бы попробовал передать массив из 100 значений и получить результаты, чтобы узнать, помогает ли это производительности.

0

Напишите свой собственный, с учетом ваших потребностей.

Например, если все ваши экспоненты имеют мощность в два, вы можете использовать бит-сдвиг. Если вы работаете с ограниченным диапазоном или набором значений, вы можете использовать справочные таблицы. Если вам не нужна точность pin-point, вы используете неточный, но более быстрый алгоритм.

0

Существует стоимость, связанная с вызовом через границу JNI.

Если вы можете переместить цикл, который вызывает exp(), в собственный код, так что есть только один нативный вызов, тогда вы можете получить лучшие результаты, но я сомневаюсь, что он будет значительно быстрее, чем чистая Java решение.

Я не знаю подробностей вашего приложения, но если у вас есть довольно ограниченный набор возможных аргументов для вызова, вы можете использовать предварительно вычисленную таблицу поиска, чтобы ускорить работу вашего Java-кода.

0

Быстрые алгоритмы для exp в зависимости от того, что вы пытаетесь выполнить. Является ли пространство проблем ограниченным для определенного диапазона, вам нужно только определенное разрешение, точность или точность и т. Д.

Если вы хорошо определили свою проблему, вы можете обнаружить, что вы можете использовать таблицу с интерполяцией, для экземпляр, который выдует почти любой другой алгоритм из воды.

Какие ограничения вы можете применить к exp, чтобы получить компромисс производительности?

-Adam

0

Я запускаю алгоритм подгонки, и минимальная ошибка результата фитинга больше , чем точность Math.exp().

Трансцендентальные функции всегда намного медленнее, чем добавление или умножение и известное узкое место. Если вы знаете, что ваши значения находятся в узком диапазоне, вы можете просто построить таблицу lookup (два сортированных массива, один вход, один вывод). Используйте Arrays.binarySearch, чтобы найти правильный индекс и интерполировать значение с элементами в [index] и [index + 1].

Другим методом является разделение номера. Возьмем, например. 3.81 и разделить это на 3 + 0.81. Теперь вы умножаете e = 2,718 три раза и получаете 20,08.

Теперь, чтобы 0,81. Все значения между 0 и 1 сходятся быстро с хорошо известной экспоненциальной серии

1 + х + х^2/2 + х^3/6 + х^4/24 .... и т.д.

Возьмите столько же, сколько вам нужно для точности; к сожалению, это медленнее, если х приближается 1. Давайте скажем, что вы переходите к x^4, тогда вы получите 2.2445 вместо правильного 2.2448

Затем умножьте результат 2.781^3 = 20.08 с 2.781^0.81 = 2.2445, и у вас есть результат 45.07 с ошибкой одной части двух тысяч (исправить: 45,15).

15

Это предложение уже было предложено несколько раз (см., Например, here). Вот приближение к Math.exp(), скопирована из this blog posting:

public static double exp(double val) { 
    final long tmp = (long) (1512775 * val + (1072693248 - 60801)); 
    return Double.longBitsToDouble(tmp << 32); 
} 

Это в основном так же, как таблицы перекодировки с 2048 записей и линейной интерполяции между записями, но все это с трюками IEEE с плавающей точкой. Его в 5 раз быстрее, чем математика.exp() на моей машине, но это может сильно измениться, если вы скомпилируете с -server.

0

Это может быть не актуально, но только в самых новых версиях OpenJDK (см. here), Math.exp следует сделать внутренним (если вы не знаете, что это такое, проверьте here).

Это сделает производительность непревзойденной для большинства архитектур, так как это означает, что виртуальная машина Hotspot заменит вызов Math.exp на конкретную для процессора реализацию exp во время выполнения. Вы никогда не сможете превзойти эти звонки, поскольку они оптимизированы для архитектуры ...

1

Commons Math3 отправлено с оптимизированной версией: FastMath.exp(double x). Это значительно ускорило мой код.

Fabien провел несколько тестов и выяснили, что это было почти в два раза быстрее, чем Math.exp():

0.75s for Math.exp  sum=1.7182816693332244E7 
0.40s for FastMath.exp sum=1.7182816693332244E7 

Вот Javadoc:

Вычисляет ехр (х), результат функции почти округлый. Он будет правильно округлен до теоретического значения для 99,9% входных значений, иначе он будет иметь ошибку 1 UPL.

Метод:

Lookup intVal = exp(int(x)) 
    Lookup fracVal = exp(int(x-int(x)/1024.0) * 1024.0); 
    Compute z as the exponential of the remaining bits by a polynomial minus one 
    exp(x) = intVal * fracVal * (1 + z) 

Точность: Расчет осуществляется с помощью 63 бит точности, поэтому результат должен быть правильно закругленные 99,9% входных значений, с менее чем 1 ULP ошибки в противном случае.