Que faire avec les performances Java BigDecimal?

J’écris des applications de trading de devises pour vivre, donc je dois travailler avec des valeurs monétaires (il est dommage que Java n’ait toujours pas de type float décimal et n’ait rien pour supporter des calculs monétaires de précision arbitraire). “Utiliser BigDecimal!” – tu pourrais dire. Je fais. Mais maintenant, j’ai du code où les performances posent problème, et BigDecimal est plus de 1000 fois (!) Plus lent que les double primitives.

Les calculs sont très simples: ce que fait le système est de calculer plusieurs fois a = (1/b) * c (où a , b et c sont des valeurs à virgule fixe). Le problème réside cependant dans ceci (1/b) . Je ne peux pas utiliser l’arithmétique en virgule fixe car il n’y a pas de point fixe. Et BigDecimal result = a.multiply(BigDecimal.ONE.divide(b).multiply(c) n’est pas seulement moche, mais lentement lent.

Que puis-je utiliser pour remplacer BigDecimal? J’ai besoin d’au moins 10 fois plus de performance. J’ai trouvé par ailleurs une excellente bibliothèque JScience qui possède une arithmétique de précision arbitraire, mais elle est encore plus lente que BigDecimal.

Aucune suggestion?

Peut-être que vous devriez commencer par remplacer a = (1 / b) * c par a = c / b? Ce n’est pas 10x, mais encore quelque chose.

Si j’étais vous, je créerais ma propre classe Money, qui conserverait de longs dollars et de longs sous, et y ferait des calculs.

Donc, ma réponse initiale était tout à fait erronée, car mon benchmark était mal écrit. Je suppose que je suis celui qui aurait dû être critiqué, pas OP;) Cela a peut-être été l’une des premières références que j’ai écrites … Eh bien, c’est comme ça que vous apprenez. Plutôt que de supprimer la réponse, voici les résultats où je ne mesure pas la mauvaise chose. Quelques notes:

  • Précalculer les tableaux pour ne pas gâcher les résultats en les générant
  • Ne jamais appeler BigDecimal.doubleValue() , car il est extrêmement lent
  • Ne plaisante pas avec les résultats en ajoutant BigDecimal s. Il suffit de renvoyer une valeur et d’utiliser une instruction if pour empêcher l’optimisation du compilateur. Assurez-vous toutefois que cela fonctionne la plupart du temps pour permettre à la twig de prédire d’éliminer cette partie du code.

Tests:

  • BigDecimal: faites le calcul exactement comme vous l’avez suggéré
  • BigDecNoRecip: (1 / b) * c = c / b, faites simplement c / b
  • Double: faites le calcul avec les doubles

Voici la sortie:

  0% Scenario{vm=java, sortingal=0, benchmark=Double} 0.34 ns; ?=0.00 ns @ 3 sortingals 33% Scenario{vm=java, sortingal=0, benchmark=BigDecimal} 356.03 ns; ?=11.51 ns @ 10 sortingals 67% Scenario{vm=java, sortingal=0, benchmark=BigDecNoRecip} 301.91 ns; ?=14.86 ns @ 10 sortingals benchmark ns linear runtime Double 0.335 = BigDecimal 356.031 ============================== BigDecNoRecip 301.909 ========================= vm: java sortingal: 0 

Voici le code:

 import java.math.BigDecimal; import java.math.MathContext; import java.util.Random; import com.google.caliper.Runner; import com.google.caliper.SimpleBenchmark; public class BigDecimalTest { public static class Benchmark1 extends SimpleBenchmark { private static int ARRAY_SIZE = 131072; private Random r; private BigDecimal[][] bigValues = new BigDecimal[3][]; private double[][] doubleValues = new double[3][]; @Override protected void setUp() throws Exception { super.setUp(); r = new Random(); for(int i = 0; i < 3; i++) { bigValues[i] = new BigDecimal[ARRAY_SIZE]; doubleValues[i] = new double[ARRAY_SIZE]; for(int j = 0; j < ARRAY_SIZE; j++) { doubleValues[i][j] = r.nextDouble() * 1000000; bigValues[i][j] = BigDecimal.valueOf(doubleValues[i][j]); } } } public double timeDouble(int reps) { double returnValue = 0; for (int i = 0; i < reps; i++) { double a = doubleValues[0][reps & 131071]; double b = doubleValues[1][reps & 131071]; double c = doubleValues[2][reps & 131071]; double division = a * (1/b) * c; if((i & 255) == 0) returnValue = division; } return returnValue; } public BigDecimal timeBigDecimal(int reps) { BigDecimal returnValue = BigDecimal.ZERO; for (int i = 0; i < reps; i++) { BigDecimal a = bigValues[0][reps & 131071]; BigDecimal b = bigValues[1][reps & 131071]; BigDecimal c = bigValues[2][reps & 131071]; BigDecimal division = a.multiply(BigDecimal.ONE.divide(b, MathContext.DECIMAL64).multiply(c)); if((i & 255) == 0) returnValue = division; } return returnValue; } public BigDecimal timeBigDecNoRecip(int reps) { BigDecimal returnValue = BigDecimal.ZERO; for (int i = 0; i < reps; i++) { BigDecimal a = bigValues[0][reps & 131071]; BigDecimal b = bigValues[1][reps & 131071]; BigDecimal c = bigValues[2][reps & 131071]; BigDecimal division = a.multiply(c.divide(b, MathContext.DECIMAL64)); if((i & 255) == 0) returnValue = division; } return returnValue; } } public static void main(String... args) { Runner.main(Benchmark1.class, new String[0]); } } 

La plupart des opérations doubles vous donnent plus que suffisamment de précision. Vous pouvez représenter 10 billions de dollars avec une précision de cent avec un double, ce qui peut être plus que suffisant pour vous.

Dans tous les systèmes de négociation sur lesquels j’ai travaillé (quatre banques différentes), ils ont utilisé le double avec un arrondi approprié. Je ne vois aucune raison d’utiliser BigDecimal.

En supposant que vous pouvez travailler avec une précision arbitraire mais connue (disons un milliardième de cent) et que vous ayez besoin d’une valeur maximale connue (un billion de milliards de dollars?), Vous pouvez écrire une classe qui stocke cette valeur un centime Vous aurez besoin de deux longs pour le représenter. Cela devrait être peut-être dix fois plus lent que d’utiliser le double; environ cent fois plus vite que BigDecimal.

La plupart des opérations ne font que l’opération sur chaque partie et la renormalisation. La division est un peu plus compliquée, mais pas beaucoup.

EDIT: En réponse au commentaire. Vous aurez besoin d’implémenter une opération bitshift sur votre classe (simple comme le multiplicateur pour la grande longueur est une puissance de deux). Faire la division déplace le diviseur jusqu’à ce qu’il ne soit pas plus grand que le dividende; soustraire le diviseur décalé du dividende et incrémenter le résultat (avec le décalage approprié). Répéter.

EDIT AGAIN: Vous pouvez trouver BigInteger fait ce dont vous avez besoin ici.

Stocker longtemps comme le nombre de cents. Par exemple, BigDecimal money = new BigDecimal ("4.20") devient de l’ long money = 420 . Vous devez juste vous souvenir de mod par 100 pour obtenir des dollars et des cents pour la sortie. Si vous avez besoin de suivre, disons, les dixièmes de cent, cela deviendrait de l’ long money = 4200 place.

Vous voudrez peut-être passer au calcul en virgule fixe. Il suffit de chercher quelques bibliothèques maintenant. sur sourceforge point fixe je n’ai pas encore regardé cela en profondeur. beartonics

Avez-vous testé avec org.jscience.economics.money? puisque cela a assuré la précision. Le point fixe ne sera aussi précis que le nombre de bits assignés à chaque pièce, mais sera rapide.

Je me souviens d’avoir assisté à une présentation d’IBM sur les ventes pour une implémentation accélérée du matériel de BigDecimal. Ainsi, si votre plate-forme cible est IBM System z ou System p, vous pouvez l’exploiter de manière transparente.

Le lien suivant peut être utile.

http://www-03.ibm.com/servers/enable/site/education/wp/181ee/181ee.pdf

Mise à jour: le lien ne fonctionne plus.

Quelle version du JDK / JRE utilisez-vous?

Vous pouvez également essayer ArciMath BigDecimal pour voir si le leur accélère pour vous.

Modifier:

Je me souviens avoir lu quelque part (je pense que c’était Java efficace) que la classe BigDecmal a été changée de JNI à une bibliothèque C à un moment donné… et cela a été plus rapide. Il se peut donc que toute bibliothèque de précision arbitraire que vous utilisez ne vous fournisse pas la vitesse dont vous avez besoin.

Personnellement, je ne pense pas que BigDecimal est idéal pour cela.

Vous voulez vraiment implémenter votre propre classe Money en utilisant des commandes en interne pour représenter la plus petite unité (c.-à-d. Cent, dixième cent). Il y a du travail là-dedans, implémentant add() et divide() etc., mais ce n’est pas si difficile.

 Only 10x performance increase desired for something that is 1000x slower than primitive?!. 

Lancer un peu plus de matériel à ce niveau pourrait être moins coûteux (compte tenu de la probabilité d’avoir une erreur de calcul de la devise).

1 / b n’est pas non plus exactement représentable avec BigDecimal. Consultez les documents de l’API pour savoir comment le résultat est arrondi.

Il ne devrait pas être trop difficile d’écrire votre propre classe décimale fixe autour d’un long champ ou deux. Je ne connais pas de librairies appropriées.

Je sais que je publie sous très vieux sujet, mais c’était le premier sujet trouvé par Google. Pensez à transférer vos calculs vers la firebase database à partir de laquelle vous prenez probablement les données pour traitement. Aussi je suis d’accord avec Gareth Davis qui a écrit:

. Dans la plupart des applications Web standard, l’access à jdbc et l’access à d’autres ressources réseau nuisent à la rapidité des calculs.

Dans la plupart des cas, les requêtes erronées ont un impact plus important sur les performances que la bibliothèque mathématique.

Pouvez-vous donner plus de détails sur le but du calcul?

Ce que vous traitez est un compromis entre vitesse et précision. Quelle sera la perte de précision si vous êtes passé à une primitive?

Je pense que dans certains cas, l’utilisateur peut être à l’aise avec moins de précision en échange de vitesse, du moment qu’il peut se concentrer sur le calcul précis en cas de besoin. Cela dépend vraiment de ce que vous utiliserez pour ce calcul.

Vous pouvez peut-être permettre à l’utilisateur de prévisualiser rapidement le résultat en utilisant les doubles, puis demander la valeur la plus précise en utilisant BigDecimal s’ils le souhaitent?

JNI est-il une possibilité? Vous pourriez être en mesure de récupérer une certaine vitesse et d’exploiter potentiellement les bibliothèques de points fixes natives existantes (peut-être même certaines qualités SSE *)

Peut-être http://gmplib.org/

Peut-être devriez-vous chercher à obtenir une arithmétique décimale accélérée par le matériel?

http://speleotrove.com/decimal/

Un problème similaire à celui-ci dans un système de négociation d’actions en 99. Au tout début de la conception, nous avons choisi d’avoir chaque numéro du système représenté comme étant un long multiplié par 1000000, donc 1.3423 était 1342300L. Mais le principal facteur était l’empreinte mémoire plutôt que la performance en ligne droite.

Un mot sur la prudence, je ne le ferais plus aujourd’hui à moins d’être vraiment sûr que la performance en mathématiques était super critique. Dans la plupart des applications Web standard, l’access à jdbc et l’access à d’autres ressources réseau nuisent à la rapidité des calculs.

Il semble que la solution la plus simple consiste à utiliser BigInteger au lieu de long pour implémenter la solution de Pesto. Si cela semble désordonné, il serait facile d’écrire une classe qui encapsule BigInteger pour masquer l’ajustement de précision.

easy … vos résultats éliminent souvent l’erreur de type de données double. Si vous faites le calcul de l’équilibre, vous devez également considérer qui sera le plus / moins de penny causé par l’arrondissement.

Le calcul de bigdeciaml produit aussi plus / moins de cent, considérez le cas 100/3.

Commons Math – La bibliothèque de mathématiques Apache Commons

http://mvnrepository.com/artifact/org.apache.commons/commons-math3/3.2

Selon mon propre benchmarking pour mon cas d’utilisation spécifique, il est 10 à 20 fois plus lent que le double (bien meilleur que 1000x), essentiellement pour l’addition / la multiplication. Après avoir analysé un autre algorithme ayant une séquence d’ajouts suivie d’une exponentiation, la diminution des performances a été un peu pire: 200x – 400x. Donc, cela semble assez rapide pour + et *, mais pas pour exp et log.

Commons Math est une bibliothèque de composants mathématiques et statistiques légers et autonomes traitant les problèmes les plus courants non disponibles dans le langage de programmation Java ou Commons Lang.

Remarque: L’API protège les constructeurs pour forcer un motif de fabrique tout en nommant la fabrique DfpField (plutôt que le DfpFac ou DfpFactory un peu plus intuitif). Donc, vous devez utiliser

 new DfpField(numberOfDigits).newDfp(myNormalNumber) 

pour instancier un Dfp, vous pouvez appeler. Je pensais le mentionner parce que c’est un peu déroutant.

Sur une JVM 64 bits, créer votre BigDecimal comme ci-dessous le rend environ 5 fois plus rapide:

 BigDecimal bd = new BigDecimal(Double.toSsortingng(d), MathContext.DECIMAL64);