web-dev-qa-db-ja.com

Java BigDecimalのパフォーマンスはどうしますか?

私は生活のための通貨取引アプリケーションを書いているので、金銭的価値を扱う必要があります(Javaには10進浮動小数点型がなく、任意精度の金銭的計算をサポートするものが何もないのは残念です) 「Use BigDecimal!」—言うかもしれませんが、今では、パフォーマンスが問題であり、BigDecimalが1000を超えるコードがあります。 doubleプリミティブよりも遅い(!).

計算は非常に簡単です。システムはa = (1/b) * cを何度も計算します(abおよびcは固定小数点値です) 。ただし、問題はこの_(1/b)_にあります。固定小数点がないため、固定小数点演算を使用できません。 BigDecimal result = a.multiply(BigDecimal.ONE.divide(b).multiply(c)はいだけでなく、遅くなります。

BigDecimalの代わりに何を使用できますか?少なくとも10倍のパフォーマンスの向上が必要です。それ以外の場合は優れた JScienceライブラリ を見つけました。これは任意精度の演算を備えていますが、BigDecimalよりもさらに低速です。

助言がありますか?

59

A =(1/b)* cをa = c/bに置き換えることから始めるべきでしょうか? 10倍ではありませんが、それでも何かです。

もし私があなたなら、私は自分のクラスMoneyを作成します。これは長いドルと長いセントを保持し、その中で数学を行います。

35

だから私のベンチマークはひどく書かれていたので、私の最初の答えは間違っていました。私は、OPではなく批判されるべきだったと思う;)これは、私がこれまでに書いた最初のベンチマークの1つであったかもしれません...まあ、それはあなたが学ぶ方法です。答えを削除するのではなく、間違ったものを測定していない結果を以下に示します。いくつかのメモ:

  • 配列を事前計算して、結果を生成して混乱させないようにします
  • everBigDecimal.doubleValue()を呼び出さないでください。非常に遅いため
  • BigDecimalsを追加して結果を台無しにしないでください。 1つの値を返し、ifステートメントを使用してコンパイラの最適化を防止します。ただし、分岐予測でコードのその部分を削除できるようにするために、ほとんどの場合は必ず機能させるようにしてください。

テスト:

  • BigDecimal:あなたが提案したとおりに数学を行う
  • BigDecNoRecip:(1/b)* c = c/b、c/bを行う
  • ダブル:ダブルスで計算を行います

出力は次のとおりです。

 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

コードは次のとおりです。

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]);
  }
}
14
durron597

ほとんどの二重演算では、十分な精度が得られます。 10兆ドルを2倍のセント精度で表すことができますが、これで十分かもしれません。

私が取り組んだすべての取引システム(4つの異なる銀行)で、適切な丸めでdoubleを使用しました。 BigDecimalを使用する理由はありません。

13
Peter Lawrey

任意の、しかし既知の精度(10億分の1セントなど)で作業でき、処理が必要な既知の最大値(1兆兆ドル?)セント。それを表すには2つのlongが必要です。それはおそらくdoubleを使用する場合の10倍遅いはずです。 BigDecimalの約100倍の速度。

ほとんどの操作は、各部分で操作を実行し、再正規化するだけです。分割は少し複雑ですが、それほど多くはありません。

編集:コメントへの応答。クラスにビットシフト演算を実装する必要があります(high longの乗数は2の累乗であるため簡単です)。除算を行うには、除数が配当よりも大きくなくなるまで除数をシフトします。被除数からシフトされた除数を減算し、結果をインクリメントします(適切なシフトを使用)。繰り返す。

もう一度編集:BigIntegerがここで必要なことを実行する場合があります。

9
DJClayworth

セント数としてlongを保存します。たとえば、BigDecimal money = new BigDecimal ("4.20")long money = 420になります。出力のためにドルとセントを得るには、100でmodすることを覚えておく必要があります。たとえば、10分の1セントを追跡する必要がある場合、代わりにlong money = 4200になります。

5
Pesto

固定小数点演算に移動したい場合があります。ちょうど今いくつかのライブラリを検索しています。 onsourceforge fixed-point これについてはまだ詳しく見ていない。 ベアトニクス

Org.jscience.economics.moneyでテストしましたか?精度が保証されているためです。固定小数点は、各ピースに割り当てられたビット数と同じくらい正確ですが、高速です。

5
sfossen

BigDecimalのハードウェアアクセラレーションの実装に関するIBMのセールスプレゼンテーションに参加したことを覚えています。したがって、ターゲットプラットフォームがIBM System zまたはSystem pである場合、これをシームレスに活用できます。

次のリンクは役に立つかもしれません。

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

更新:リンクは機能しなくなりました。

3
toolkit

使用しているJDK/JREのバージョンは何ですか?

また、 ArciMath BigDecimal を試して、その速度が向上するかどうかを確認することもできます。

編集:

BigDecmalクラスがCライブラリへのJNI呼び出しからすべてのJava ...に変更されたことを...どこかで...したがって、使用する任意の精度ライブラリでは、必要な速度が得られない可能性があります。

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

これでもう少しハードウェアを投げる方が安くなる可能性があります(通貨計算エラーが発生する可能性を考慮して)。

2
Ryan Fernandes

個人的には、BigDecimalはこれには理想的ではないと思います。

最小単位(セント、10セント)を表すために内部的にlongを使用して独自のMoneyクラスを実装する必要があります。 add()divide()などを実装する作業がいくつかありますが、それほど難しくありません。

2
SCdF

簡単...結果を丸めることで、二重データ型のエラーを排除できます。残高の計算を行っている場合は、丸めによって生じるペニーを所有するかどうかを考慮する必要があります。

bigdeciamlの計算では、ペニーがより多く/より少なくなります。100/ 3の場合を考慮してください。

1
Chris

1/bもBigDecimalでは正確に表現できません。 APIドキュメントを参照して、結果がどのように丸められるかを確認してください。

tooは、長いフィールドまたは2つのフィールドに基づいて独自の固定10進数クラスを記述することは困難です。適切な既製のライブラリがわかりません。

私は非常に古いトピックの下で投稿していることを知っていますが、これはグーグルが見つけた最初のトピックでした。処理のためにデータを取得するデータベースに計算を移動することを検討してください。また、私は書いたGareth Davisに同意します。

。ほとんどの沼地の標準webアプリでは、jdbcアクセスと他のネットワークリソースへのアクセスのオーバーヘッドが、本当に素早い計算の利点を圧倒します。

ほとんどの場合、間違ったクエリは数学ライブラリよりもパフォーマンスに大きな影響を与えます。

1
Marek Kowalczyk

99年の株式取引システムでこれと同様の問題がありました。設計の最初の段階で、システム内のすべての数値をlongに1000000を掛けたものとして選択し、1.3423は1342300Lでした。しかし、これの主な要因は、直線的なパフォーマンスではなく、メモリフットプリントでした。

ある言葉に注意してください。私が今日、これをやり直すことはありません本当に数学のパフォーマンスが非常に重要であることを確認してください。ほとんどの沼地の標準webアプリでは、jdbcアクセスと他のネットワークリソースへのアクセスのオーバーヘッドが、本当に素早い計算の利点を圧倒します。

0
Gareth Davis

最も簡単な解決策は、ペストの解決策を実装するために長いのではなくBigIntegerを使用することであるようです。面倒な場合は、BigIntegerをラップして精度調整を隠すクラスを作成するのは簡単です。

0
ozone

JNIは可能ですか?ある程度の速度を回復し、既存のネイティブ固定小数点ライブラリを活用できる可能性があります(おそらくSSE *の良さも)

おそらく http://gmplib.org/

0
basszero

計算の目的についてより多くの洞察を提供できますか?

対処するのは、速度と精度のトレードオフです。プリミティブに切り替えた場合、精度の低下はどれほど大きいでしょうか?

場合によっては、必要なときに正確な計算に焦点を合わせることができる限り、ユーザーは速度と引き換えに精度が低くても快適であると思います。これは、この計算を何に使用するかによります。

おそらく、doublesを使用してユーザーが結果をすばやくプレビューし、必要に応じてBigDecimalを使用してより正確な値を要求できるようにすることができますか?

0
Justin Standard

Commons Math-Apache Commons数学ライブラリ

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

私の特定のユースケースのための私自身のベンチマークによると、それは2倍よりも10から20倍遅い(1000倍よりはるかに良い)-基本的に加算/乗算のためです。追加とそれに続くべき乗のシーケンスを持つ別のアルゴリズムのベンチマークを行った後、パフォーマンスの低下はかなり悪くなりました:200x-400x。そのため、+と*はかなり高速に見えますが、expとlogは高速ではありません。

Commons Mathは、Javaプログラミング言語またはCommons Lang。]では利用できない最も一般的な問題に対処する、軽量の自己完結型数学および統計コンポーネントのライブラリです。

注:APIは、コンストラクターを保護して、ファクトリーDfpFieldに名前を付けるときにファクトリー・パターンを強制します(多少直感的なDfpFacまたはDfpFactoryではありません)。だから、使用する必要があります

new DfpField(numberOfDigits).newDfp(myNormalNumber)

dfpをインスタンス化するには、.multiplyまたはこれに関するもの。私はこれを少し混乱させるので言及すると思いました。

0
samthebest

以下のようにBigDecimalを作成する64ビットJVMでは、約5倍高速になります。

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

たぶん、ハードウェアアクセラレーションによる10進数演算を検討する必要がありますか?

http://speleotrove.com/decimal/

0
John Nilsson