Question

Je dois calculer très fréquemment Math.exp () à partir de Java. Est-il possible d'obtenir une version native plus rapidement que celle de Java , .exp () ??

J'ai juste essayé jni + C, mais c'est plus lent que java .

Était-ce utile?

La solution

+1 pour écrire votre propre implémentation exp (). Autrement dit, si cela est vraiment un goulot d'étranglement dans votre application. Si vous pouvez traiter un peu d'inexactitude, il existe un certain nombre d'algorithmes d'estimation des exposants extrêmement efficaces, dont certains datent de plusieurs siècles. Si j'ai bien compris, l'implémentation de Java (exp ()) est assez lente, même pour les algorithmes qui doivent retourner "exact". résultats.

Oh, et n’ayez pas peur d’écrire cette implémentation exp () en Java pur. JNI a beaucoup de travail, et la JVM est capable d’optimiser le bytecode au moment de l’exécution, parfois même au-delà de ce que C / C ++ est capable d’atteindre.

Autres conseils

Cela a déjà été demandé à plusieurs reprises (voir, par exemple, ici ). Voici une approximation de Math.exp (), copiée à partir de cet article de blog :

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

C'est en gros la même chose qu'une table de correspondance avec 2 048 entrées et une interpolation linéaire entre les entrées, mais tout cela avec des astuces à virgule flottante IEEE. Il est 5 fois plus rapide que Math.exp () sur ma machine, mais cela peut varier considérablement si vous compilez avec -server.

Utilisez Java.

Cachez également les résultats de l'exp et vous pourrez alors rechercher la réponse plus rapidement que de les calculer à nouveau.

Vous voudriez envelopper n'importe quelle boucle appelant Math.exp () en C également. Sinon, la surcharge de marshalling entre Java et C surchargera tout avantage en performances.

Vous pourrez peut-être le faire fonctionner plus rapidement si vous le faites par lots. Faire un appel JNI ajoute une surcharge, donc vous ne voulez pas le faire pour chaque exp () que vous devez calculer. J'essaierais de passer un tableau de 100 valeurs et d'obtenir les résultats pour voir si cela améliore les performances.

La vraie question est, est-ce que cela est devenu un goulot d'étranglement pour vous? Avez-vous profilé votre application et constaté que cela constituait une cause majeure de ralentissement?

Si ce n'est pas le cas, je vous recommanderais d'utiliser la version de Java. Essayez de ne pas pré-optimiser car cela ralentirait le développement. Vous pouvez passer beaucoup de temps sur un problème qui risque de ne pas être un problème.

Cela étant dit, je pense que votre test vous a donné votre réponse. Si jni + C est plus lent, utilisez la version de Java.

Commons Math3 est fourni avec une version optimisée: FastMath.exp ( double x) . Cela a considérablement accéléré mon code.

Fabien a lancé des tests et a découvert qu'il était presque deux fois plus rapide que Math.exp () :

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

Voici le javadoc:

Calcule exp (x), le résultat de la fonction est presque arrondi. Il sera correctement arrondi à la valeur théorique pour 99,9% des valeurs entrées, sinon il générera une erreur de 1 UPL.

Méthode:

    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)

Précision: le calcul est effectué avec une précision de 63 bits. Le résultat doit donc être arrondi correctement pour 99,9% des valeurs en entrée, avec moins d'une erreur ULP sinon.

Etant donné que le code Java sera compilé en code natif avec le compilateur JIT (Just-In-Time), il n’ya vraiment aucune raison d’utiliser JNI pour appeler du code natif.

De plus, vous ne devriez pas mettre en cache les résultats d'une méthode où les paramètres d'entrée sont des nombres réels à virgule flottante. Les gains obtenus dans le temps seront très fortement perdus en quantité d'espace utilisé.

Le problème de l’utilisation de JNI est la surcharge liée à l’appel à JNI. La machine virtuelle Java est plutôt optimisée de nos jours et les appels à la Math.exp () intégrée sont automatiquement optimisés pour appeler directement à la fonction C exp (). Ils pourraient même être optimisés dans un assemblage à virgule flottante x87 direct. instructions.

Il y a simplement une surcharge associée à l'utilisation de JNI, voir aussi: http://java.sun.com/docs/ livres / performance / 1st_edition / html / JPNativeCode.fm.html

Comme d'autres l'ont suggéré, essayez de regrouper les opérations impliquant l'utilisation de JNI.

Ecrivez le vôtre, adapté à vos besoins.

Par exemple, si tous vos exposants ont une puissance de deux, vous pouvez utiliser le transfert de bits. Si vous travaillez avec une plage ou un ensemble de valeurs limité, vous pouvez utiliser des tables de recherche. Si vous n'avez pas besoin d'une précision ponctuelle, vous utilisez un algorithme imprécis, mais plus rapide.

L’appel au-delà de la limite JNI a un coût.

Si vous pouviez également déplacer la boucle qui appelle exp () dans le code natif, de sorte qu'il n'y ait qu'un seul appel natif, vous obtiendrez peut-être de meilleurs résultats, mais je doute que ce soit beaucoup plus rapide que la solution Java pure. .

Je ne connais pas les détails de votre application, mais si vous avez un ensemble assez limité d'arguments possibles pour l'appel, vous pouvez utiliser une table de correspondance pré-calculée pour rendre votre code Java plus rapide.

Il existe des algorithmes plus rapides pour exp selon ce que vous essayez d'accomplir. L’espace du problème est-il limité à une certaine plage, avez-vous seulement besoin d’une résolution, d’une précision ou d’une exactitude déterminées, etc.

Si vous définissez très bien votre problème, vous constaterez peut-être que vous pouvez utiliser un tableau avec interpolation, par exemple, qui supprimera presque tout autre algorithme de l'eau.

Quelles contraintes pouvez-vous appliquer à exp pour obtenir ce compromis de performance?

-Adam

  

Je lance un algorithme d'adaptation et l'erreur minimale du résultat de l'ajustement est beaucoup plus grande   que la précision de Math.exp ().

Les fonctions transcendantales sont toujours beaucoup plus lentes que l’addition ou la multiplication et un goulot d’étranglement bien connu. Si vous savez que vos valeurs se situent dans une plage étroite, vous pouvez simplement créer une table de correspondance (Deux tableaux triés; une entrée, une sortie). Utilisez Arrays.binarySearch pour trouver le bon index et interpoler la valeur avec les éléments situés dans [index] et [index + 1].

Une autre méthode consiste à fractionner le nombre. Prenons par exemple 3,81 et diviser cela en 3 + 0,81. Maintenant, vous multipliez e = 2.718 trois fois et obtenez 20.08.

Maintenant à 0.81. Toutes les valeurs comprises entre 0 et 1 convergent rapidement avec la série exponentielle bien connue

1 + x + x ^ 2/2 + x ^ 3/6 + x ^ 4/24 ... etc.

Prenez autant de termes que nécessaire pour plus de précision. Malheureusement, c'est plus lent si x approche 1. Disons que vous allez à x ^ 4, alors vous obtenez 2.2445 au lieu des 2.2448 correctes

Puis multipliez le résultat 2,781 ^ 3 = 20,08 par 2,781 ^ 0,81 = 2,2445 et vous obtiendrez le résultat 45.07 avec une erreur d'une partie de deux mille (correct: 45.15).

Cela peut ne plus être pertinent, mais juste pour votre information, dans les dernières versions d'OpenJDK (voir ici ), Math.exp doit devenir un élément intrinsèque (si vous ne savez pas ce que cela signifie, vérifiez ici ).

Cela rendra les performances imbattables sur la plupart des architectures, car cela signifie que la machine virtuelle Hotspot remplacera l'appel de Math.exp par une implémentation exp spécifique au processeur, au moment de l'exécution. Vous ne pouvez jamais battre ces appels, car ils sont optimisés pour l'architecture ...

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top