wake-up-neo.com

Was ist mit der Java BigDecimal-Leistung zu tun?

Ich schreibe Devisenhandelsanwendungen, um zu leben, also muss ich mit monetären Werten arbeiten (es ist eine Schande, dass Java noch keinen dezimalen Float-Typ hat und nichts zur Unterstützung von Währungsberechnungen mit beliebiger Genauigkeit hat). "Verwenden Sie BigDecimal!" - man könnte sagen. Ich mache. Aber jetzt habe ich etwas Code, bei dem die Performance is ein Problem ist und BigDecimal mehr als 1000 Mal (!) Langsamer ist als double-Primitiven.

Die Berechnungen sind sehr einfach: Was das System tut, berechnet a = (1/b) * c viele Male (wobei a, b und c Festkommawerte sind). Das Problem liegt jedoch bei diesem (1/b). Ich kann keine Festkomma-Arithmetik verwenden, da es keinen Festpunkt gibt. Und BigDecimal result = a.multiply(BigDecimal.ONE.divide(b).multiply(c) ist nicht nur hässlich, sondern träge langsam.

Was kann ich verwenden, um BigDecimal zu ersetzen? Ich brauche mindestens die 10-fache Leistungssteigerung. Ich fand ansonsten eine ausgezeichnete JScience-Bibliothek , die Arithmetik mit beliebiger Genauigkeit hat, aber sie ist noch langsamer als BigDecimal.

Irgendwelche Vorschläge?

58

Möglicherweise sollten Sie damit beginnen, a = (1/b) * c durch a = c/b zu ersetzen. Es ist nicht 10x, aber immer noch etwas.

Wenn ich Sie wäre, würde ich meine eigene Klasse Money erstellen, die lange Dollars und Cent enthalten würde, und darin rechnen. 

34

Meine ursprüngliche Antwort war einfach falsch, weil mein Benchmark schlecht geschrieben wurde. Ich denke, ich bin derjenige, der hätte kritisiert werden sollen, nicht OP;) Dies war möglicherweise einer der ersten Benchmarks, die ich je geschrieben habe ... na ja, so lernen Sie. Anstatt die Antwort zu löschen, sind hier die Ergebnisse, bei denen ich nicht das Falsche messe. Einige Notizen:

  • Berechnen Sie die Arrays so vor, dass ich sie nicht durch Generieren mit den Ergebnissen vermische
  • Nicht ever rufen Sie BigDecimal.doubleValue() auf, da es extrem langsam ist
  • Machen Sie sich nicht mit den Ergebnissen herum, indem Sie BigDecimals hinzufügen. Geben Sie einfach einen Wert zurück und verwenden Sie eine if -Anweisung, um die Compiler-Optimierung zu verhindern. Stellen Sie sicher, dass es die meiste Zeit funktioniert, damit die Verzweigungsvorhersage diesen Teil des Codes eliminiert.

Tests:

  • BigDecimal: Rechne genau so, wie du es vorgeschlagen hast
  • BigDecNoRecip: (1/b) * c = c/b, tun Sie einfach c/b
  • Double: Rechnen Sie mit Double

Hier ist die Ausgabe:

 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

Hier ist der 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]);
  }
}
13
durron597

Die meisten Doppeloperationen bieten Ihnen mehr als genug Präzision. Sie können 10 Billionen US-Dollar mit einer Cent-Genauigkeit darstellen, die doppelt so hoch ist, was für Sie mehr als genug ist.

In allen Handelssystemen, an denen ich gearbeitet habe (vier verschiedene Banken), haben sie bei entsprechender Rundung doppelt verwendet. Ich sehe keinen Grund, BigDecimal zu verwenden.

13
Peter Lawrey

Angenommen, Sie können mit einer beliebigen, aber bekannten Genauigkeit arbeiten (beispielsweise ein Milliardstel eines Centes) und einen bekannten Maximalwert (eine Billion Billion Dollar?) Haben, den Sie benötigen, können Sie eine Klasse schreiben, die diesen Wert als ganze Zahl von Milliardstel speichert ein Cent Du brauchst zwei Longs, um es darzustellen. Das sollte etwa zehnmal so langsam sein wie bei der Verwendung von double. etwa hundertmal so schnell wie BigDecimal.

Die meisten Vorgänge führen die Vorgänge nur für jeden Teil durch und führen zur Renormierung. Die Aufteilung ist etwas komplizierter, aber nicht viel.

BEARBEITEN: Als Antwort auf den Kommentar. Sie müssen eine Bit-Shift-Operation in Ihrer Klasse implementieren (so einfach wie der Multiplikator für das hohe Lang ist eine Zweierpotenz). Um eine Division durchzuführen, verschieben Sie den Divisor, bis er nicht ganz größer als die Dividende ist. verschobenen Divisor von der Dividende subtrahieren und das Ergebnis erhöhen (bei entsprechender Verschiebung). Wiederholen.

BEARBEITEN SIE WIEDER: Sie finden, dass BigInteger hier das tut, was Sie brauchen.

9
DJClayworth

Speichern Sie die Anzahl der Cents. Zum Beispiel wird BigDecimal money = new BigDecimal ("4.20") zu long money = 420. Sie müssen nur daran denken, um 100 zu modulieren, um Dollar und Cent für die Ausgabe zu erhalten. Wenn Sie beispielsweise Zehntel eines Centes nachverfolgen müssen, würde er stattdessen long money = 4200 werden.

5
Pesto

Möglicherweise möchten Sie zur Festpunktberechnung wechseln. Suchen Sie einfach nach ein paar Bibliotheken ... auf sourceforge fixed-point Ich habe mir das noch nicht eingehend angeschaut. beartonics

Haben Sie mit org.jscience.economics.money getestet? denn das hat Genauigkeit garantiert. Der Festpunkt ist nur so genau wie die Anzahl der jedem Stück zugewiesenen Bits, ist aber schnell.

5
sfossen

Ich erinnere mich an eine Verkaufspräsentation von IBM für eine hardwarebeschleunigte Implementierung von BigDecimal. Wenn Ihre Zielplattform also IBM System z oder System p ist, können Sie dies nahtlos ausnutzen.

Der folgende Link kann von Nutzen sein.

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

Update: Link funktioniert nicht mehr.

3
toolkit

Welche Version des JDK/JRE verwenden Sie?

Möglicherweise versuchen Sie auch ArciMath BigDecimal , um zu sehen, ob es für Sie schneller wird.

Bearbeiten:

Ich erinnere mich, dass ich irgendwo gelesen habe (ich glaube, es war Effective Java), dass die BigDecmal-Klasse geändert wurde, als JNI in eine C-Bibliothek gerufen wurde und irgendwann in alles Java konvertiert wurde. Es kann also sein, dass eine beliebige Bibliothek mit beliebiger Genauigkeit nicht die Geschwindigkeit erreicht, die Sie benötigen.

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

Etwas mehr Hardware zu verwenden, ist möglicherweise billiger (in Anbetracht der Wahrscheinlichkeit eines Währungsberechnungsfehlers). 

2
Ryan Fernandes

Ich persönlich glaube nicht, dass BigDecimal dafür ideal ist.

Sie möchten wirklich Ihre eigene Money-Klasse implementieren, indem Sie longs intern verwenden, um die kleinste Einheit darzustellen (d. H. Cent, 10. Cent). Es gibt einige Arbeit darin, add() und divide() etc zu implementieren, aber es ist nicht wirklich so schwer.

2
SCdF

einfach ... Runden Sie Ihre Ergebnisse häufig aus, um den Fehler des doppelten Datentyps zu beseitigen. Wenn Sie die Balance-Berechnung durchführen, müssen Sie auch überlegen, wer den durch die Rundung verursachten mehr/weniger Pfennig besitzt. 

bigdeciaml-Berechnung produziert auch mehr/weniger Penny, betrachten Sie den Fall 100/3.

1
Chris

1/b ist mit BigDecimal ebenfalls nicht exakt darstellbar. In den API-Dokumenten erfahren Sie, wie das Ergebnis gerundet wird.

Es sollte nicht zu _ schwierig sein, eine eigene dezimale Klasse basierend auf einem oder zwei langen Feldern zu schreiben. Ich kenne keine geeigneten Bibliotheken.

Ich weiß, dass ich unter einem sehr alten Thema poste, aber dies war das erste Thema, das von google ..__ gefunden wurde. Ziehen Sie in Betracht, Ihre Berechnungen in die Datenbank zu verschieben, aus der Sie wahrscheinlich die Daten zur Verarbeitung übernehmen. Ich stimme auch mit Gareth Davis überein, der schrieb:

. In den meisten Standard-Webapps ist der Overhead des jdbc-Zugriffs und der Zugriff auf andere Netzwerke __. Ressourcen überschwemmen jeden Vorteil von wirklich schneller Mathematik.

In den meisten Fällen haben falsche Abfragen einen höheren Einfluss auf die Leistung als die Mathematikbibliothek.

1
Marek Kowalczyk

Hatte ein ähnliches Problem in einem Aktienhandelssystem im Jahr 99. Zu Beginn des Entwurfs haben wir uns dafür entschieden, jede Zahl im System als Long multipliziert mit 1000000 darzustellen, also 1,3423 = 1342300L. Der Haupttreiber dafür war jedoch der Speicherbedarf und nicht die Leistung auf gerader Linie.

Ein Wort zur Vorsicht, ich würde dies heute nicht noch einmal tun, wenn ich nicht wirklich sicher wäre, dass die mathematische Leistung überaus kritisch ist. In den meisten Standard-Webanwendungen überflutet der Aufwand für den Zugriff auf JDBC und andere Netzwerkressourcen den Vorteil einer wirklich schnellen Berechnung.

0
Gareth Davis

Die einfachste Lösung scheint die Verwendung von BigInteger zu sein, um die Lösung von pesto zu implementieren. Wenn es unordentlich erscheint, wäre es leicht, eine Klasse zu schreiben, die BigInteger einschließt, um die Präzisionsanpassung auszublenden.

0
ozone

Ist JNI eine Möglichkeit? Möglicherweise können Sie etwas Geschwindigkeit wiederherstellen und vorhandene native Festkomma-Bibliotheken potenziell nutzen (möglicherweise sogar einige SSE-Werte).

Vielleicht http://gmplib.org/

0
basszero

Können Sie mehr Einsicht in den Zweck der Berechnung geben? 

Was Sie tun, ist ein Kompromiss zwischen Geschwindigkeit und Präzision. Wie groß ist der Genauigkeitsverlust, wenn Sie zu einem Primitiv wechseln?

Ich denke, in manchen Fällen mag der Benutzer weniger Genauigkeit im Austausch gegen Geschwindigkeit haben, solange er die genaue Berechnung bei Bedarf verbessern kann. Es hängt wirklich davon ab, wofür Sie diese Berechnung verwenden werden.

Vielleicht können Sie dem Benutzer erlauben, das Ergebnis mit Hilfe von Doubles schnell in der Vorschau anzuzeigen und dann den präziseren Wert mit BigDecimal anzufordern, wenn er möchte.

0
Justin Standard

Commons Math - Die Mathematikbibliothek von Apache Commons

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

Nach meinem eigenen Benchmarking für meinen spezifischen Anwendungsfall ist es 10 - 20x langsamer als das Doppelte (viel besser als 1000x) - im Wesentlichen für die Addition/Multiplikation. Nach dem Benchmarking eines anderen Algorithmus mit einer Folge von Additionen, gefolgt von einer Potenzierung, war der Leistungsabfall etwas schlechter: 200x - 400x. Es scheint also ziemlich schnell für + und *, aber nicht für exp und log.

Commons Math ist eine Bibliothek aus leichtgewichtigen, eigenständigen Mathematik- und Statistikkomponenten, die sich mit den häufigsten Problemen befassen, nicht mit verfügbar in der Programmiersprache Java oder Commons Lang.

Hinweis: Die API schützt die Konstruktoren, um ein Factory-Muster zu erzwingen, während das Factory-DfpField-Objekt (anstatt des etwas intuitiveren DfpFac oder DfpFactory) benannt wird. Also musst du verwenden 

new DfpField(numberOfDigits).newDfp(myNormalNumber)

um ein Dfp zu instanziieren, können Sie .multiply oder was auch immer dazu aufrufen. Ich dachte, ich würde das erwähnen, weil es etwas verwirrend ist.

0
samthebest

Bei einer 64-Bit-JVM können Sie beim Erstellen Ihres BigDecimal-Codes den Wert etwa 5-mal schneller einstellen:

BigDecimal bd = new BigDecimal(Double.toString(d), MathContext.DECIMAL64);
0
tsquared

Vielleicht sollten Sie sich mit hardwarebeschleunigten Dezimalarithmetik beschäftigen?

http://speleotrove.com/decimal/

0
John Nilsson