66 votes

Que faire avec les performances Java BigDecimal?

J'écris le trading de devises applications pour la vie, donc je dois travailler avec des valeurs monétaires (c'est une honte que Java n'ai toujours pas decimal float type et n'a rien à soutien de précision arbitraire des calculs monétaires). "L'Utilisation BigDecimal!" - pourrait-on dire. Je ne. Mais maintenant j'ai un code où la performance est un problème, et BigDecimal est plus de 1000 fois (!) plus lent que l' double primitives.

Les calculs sont très simples: ce que le système ne calcule a = (1/b) * c de très nombreuses fois (où a, b et c sont point fixe de valeurs). Le problème, cependant, réside avec cette (1/b). Je ne peux pas utiliser de point fixe de l'arithmétique, car il n'y a pas de point fixe. Et BigDecimal result = a.multiply(BigDecimal.ONE.divide(b).multiply(c) n'est pas seulement laid, mais lentement lentement.

Que puis-je utiliser pour remplacer BigDecimal? J'ai besoin d'au moins 10x augmentation de la performance. J'ai trouvé par ailleurs un excellent JScience bibliothèque qui a une précision arbitraire arithmétique, mais il est encore plus lent que BigDecimal.

Toutes les suggestions?

39voto

Vladimir Dyuzhev Points 10647

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

Si j'étais vous, je créerais ma propre classe Money, qui garderait des dollars longs et des cents longs et ferait les calculs en conséquence.

20voto

Peter Lawrey Points 229686

La plupart des doubles opérations vous donnent plus de précision. Vous pouvez représenter 10 000 milliards de dollars avec une précision d'un 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.

15voto

durron597 Points 9165

Donc, ma réponse était juste à plat de mal, parce que mon test a été écrit mal. Je suppose que je suis le seul qui doit avoir été critiqué, pas OP ;) Cela peut avoir été l'un des premiers benchmarks que j'ai jamais écrit... eh bien, c'est la façon dont vous apprenez. Plutôt que de supprimer la réponse, voici les résultats, si je ne suis pas en mesure de quelque chose de mal. Quelques remarques:

  • Précalculer les tableaux donc je ne plaisante pas avec les résultats en générant eux
  • Ne jamais appeler BigDecimal.doubleValue(), comme il est extrêmement lent
  • Ne salissez pas avec les résultats en ajoutant BigDecimals. Il suffit de retourner une valeur, et d'utiliser une instruction if pour empêcher l'optimisation du compilateur. Assurez-vous de faire travailler la plupart du temps pour permettre à la branche de prédiction afin d'éliminer la partie du code.

Tests:

  • BigDecimal: faites le calcul exactement comme vous l'a suggéré
  • BigDecNoRecip: (1/b) * c = c/b, il suffit de faire c/b
  • Double: faire le calcul avec des doubles

Voici le résultat:

 0% Scenario{vm=java, trial=0, benchmark=Double} 0.34 ns; ?=0.00 ns @ 3 trials
33% Scenario{vm=java, trial=0, benchmark=BigDecimal} 356.03 ns; ?=11.51 ns @ 10 trials
67% Scenario{vm=java, trial=0, benchmark=BigDecNoRecip} 301.91 ns; ?=14.86 ns @ 10 trials

    benchmark      ns linear runtime
       Double   0.335 =
   BigDecimal 356.031 ==============================
BigDecNoRecip 301.909 =========================

vm: java
trial: 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]);
  }
}

9voto

DJClayworth Points 11288

En supposant que vous pouvez fonctionner jusqu'à un certain arbitraire, mais connu de précision (un milliardième de cent) et une valeur maximale, vous avez besoin de la poignée (un de trillions de trillions de dollars?) vous pouvez écrire une classe qui stocke cette valeur comme un nombre entier de milliardièmes de cent. Vous aurez besoin de deux longs pour le représenter. Qui devrait être peut-être dix fois plus lent que l'utilisation de double; une centaine de fois plus rapide que BigDecimal.

La plupart des opérations sont la simple exécution de l'opération sur chaque partie et renormer. La Division est un peu plus compliqué, mais pas beaucoup.

EDIT:En réponse à l'observation. Vous devrez mettre en place un bitshift fonctionnement de votre classe (facile que de le long que le multiplicateur pour le haut de long est une puissance de deux). Faire de décalage de la division, le diviseur jusqu'à ce qu'il n'est pas beaucoup plus grand que le dividende; soustraire décalé diviseur dividende et incrémenter le résultat (avec les maj). Répétez.

MODIFIER à NOUVEAU:Vous pouvez trouver BigInteger fait ce que vous avez besoin ici.

5voto

Pesto Points 16648

Stocke en tant que nombre de centimes. Par exemple, BigDecimal money = new BigDecimal ("4.20") devient long money = 420 . N'oubliez pas de modifier 100 pour obtenir des dollars et des cents pour la sortie. Si vous avez besoin de suivre, disons, des dixièmes de cent, cela deviendrait plutôt long money = 4200 .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X