Вопрос

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

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

Это было полезно?

Решение

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

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

Другие советы

Это уже было запрошено несколько раз (см., например, здесь).Вот приближение к Math.exp(), скопированное из это сообщение в блоге:

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

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

Используйте Java.

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

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

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

Настоящий вопрос в том, стало ли это для вас узким местом?Вы профилировали свое приложение и обнаружили, что это основная причина замедления?

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

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

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

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

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

Вот Javadoc:

Вычисляет exp(x), результат функции почти округляется.Оно будет правильно округлено до теоретического значения для 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% входных значений, в противном случае ошибка ULP должна быть менее 1.

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

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

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

Просто использование JNI связано с накладными расходами, см. также:http://java.sun.com/docs/books/ Performance/1st_edition/html/JPNativeCode.fm.html

Итак, как предлагали другие, попробуйте сопоставить операции, которые предполагают использование JNI.

Напишите свой собственный, адаптированный к вашим потребностям.

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

За вызов через границу JNI приходится платить.

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

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

Существуют более быстрые алгоритмы получения опыта в зависимости от того, чего вы пытаетесь достичь.Ограничено ли проблемное пространство определенным диапазоном, нужна ли вам только определенная разрешающая способность, точность или аккуратность и т. д.

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

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

-Адам

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

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

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

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

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

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

Затем умножьте результат 2,781^3 = 20,08 на 2,781^0,81 = 2,2445, и у вас будет результат 45,07 с ошибкой одной части двух тысяч (правильно:45.15).

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top