Быстрее Math.exp() через JNI?

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

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

Ответ 1

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

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

Ответ 2

Это уже запрошено несколько раз (см. здесь). Ниже приведено приближение к 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.

Ответ 3

Использовать Java.

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

Ответ 4

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

Ответ 5

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

Ответ 6

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

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

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

Ответ 7

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:

Вычисляет 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% входных значений с менее чем 1 ошибкой ULP.

Ответ 8

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

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

Ответ 9

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

Ответ 10

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

Так как другие предложили попытаться сопоставить операции, которые связаны с использованием JNI.

Ответ 11

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

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

Ответ 12

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

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

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

Ответ 13

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

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

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

-Adam

Ответ 14

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

Трансцендентальные функции всегда намного медленнее, чем добавление или умножение и известное узкое место. Если вы знаете, что ваши значения находятся в узком диапазоне, вы можете просто построить таблицу lookup (два сортированных массива, один вход, один вывод). Используйте 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.... и т.д.

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

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

Ответ 15

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

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