Development Tip

Java BigDecimal 성능으로 무엇을 할 수 있습니까?

yourdevel 2020. 12. 8. 20:08
반응형

Java BigDecimal 성능으로 무엇을 할 수 있습니까?


저는 생활용 통화 거래 응용 프로그램을 작성하기 때문에 화폐 가치로 작업해야합니다 (Java가 여전히 십진수 부동 유형이없고 임의 정밀도의 화폐 계산을 지원할 것이 없다는 점이 아쉽습니다). "BigDecimal을 사용하십시오!" — 당신은 말할 수 있습니다. 나는한다. 그러나 이제 성능 문제가되고 BigDecimal이 double기본 요소 보다 1000 배 이상 느린 코드가 있습니다.

시스템이 계산되는 기능 : 계산은 매우 간단 a = (1/b) * c많은 여러 번 ( a, bc고정 소수점 값이다). 그러나 문제는 이것에 (1/b)있습니다. 고정 소수점이 없기 때문에 고정 소수점 산술을 사용할 수 없습니다. 그리고 BigDecimal result = a.multiply(BigDecimal.ONE.divide(b).multiply(c)추악 할뿐만 아니라 느리게 느립니다.

BigDecimal을 대체하기 위해 무엇을 사용할 수 있습니까? 최소한 10 배 이상의 성능 향상이 필요합니다. 임의 정밀도 산술이있는 우수한 JScience 라이브러리찾았 지만 BigDecimal보다 훨씬 느립니다.

어떤 제안?


a = (1 / b) * c를 a = c / b로 바꾸는 것으로 시작해야할까요? 10x는 아니지만 여전히 뭔가.

내가 당신이라면, 나는 긴 달러와 긴 센트를 유지하고 그 안에서 수학을 할 나만의 돈을 만들 것입니다.


그래서 내 원래 대답은 내 벤치 마크가 잘못 작성 되었기 때문에 잘못되었습니다. 나는 OP가 아니라 비판을 받았어야하는 사람이라고 생각한다;) 이것은 내가 쓴 첫 번째 벤치 마크 중 하나일지도 모른다 ... 오 글쎄, 그것이 당신이 배우는 방법입니다. 답을 삭제하는 대신 잘못된 것을 측정하지 않은 결과는 다음과 같습니다. 몇 가지 참고 사항 :

  • 배열을 미리 계산하여 결과를 생성하여 엉망으로 만들지 않도록합니다.
  • 매우 느리기 때문에 절대 전화 하지 마십시오.BigDecimal.doubleValue()
  • BigDecimals 를 추가하여 결과를 엉망으로 만들지 마십시오 . 하나의 값만 반환하고 if 문을 사용하여 컴파일러 최적화를 방지하십시오. 하지만 브랜치 예측이 코드의 해당 부분을 제거 할 수 있도록 대부분의 시간에 작동하도록해야합니다.

테스트 :

  • BigDecimal : 제안한대로 정확하게 계산
  • BigDecNoRecip : (1 / b) * c = c / b, 그냥 c / b
  • Double : 복식으로 수학하기

다음은 출력입니다.

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

대부분의 이중 연산은 충분한 정밀도 이상을 제공합니다. 2 배의 정확도로 10 조 달러를 표현할 수 있습니다.

In all the trading systems I have worked on (four different banks), they have used double with appropriate rounding. I don't see any reason to be using BigDecimal.


Assuming you can work to some arbitrary but known precision (say a billionth of a cent) and have a known maximum value you need handle (a trillion trillion dollars?) you can write a class which stores that value as an integer number of billionths of a cent. You'll need two longs to represent it. That should be maybe ten times as slow as using double; about a hundred times as fast as BigDecimal.

Most of the operations are just performing the operation on each part and renormalizing. Division is slightly more complicated, but not much.

EDIT:In response to the comment. You will need to implement a bitshift operation on your class (easy as along as the multiplier for the high long is a power of two). To do division shift the divisor until it's not quite bigger than the dividend; subtract shifted divisor from dividend and increment the result (with appropriate shift). Repeat.

EDIT AGAIN:You may find BigInteger does what you need here.


Store longs as the number of cents. For example, BigDecimal money = new BigDecimal ("4.20") becomes long money = 420. You just have to remember to mod by 100 to get dollars and cents for output. If you need to track, say, tenths of a cent, it'd become long money = 4200 instead.


You might want to move to fixed point math. Just searching for some libraries right now. on sourceforge fixed-point I haven't looked at this in depth yet. beartonics

Did you test with org.jscience.economics.money? since that has assured accuracy. The fixed point will only be as accurate as the # of bits assigned to each piece, but is fast.


I remember attending a sales presentation from IBM for a hardware accelerated implementation of BigDecimal. So if your target platform is IBM System z, or System p, you could exploit this seamlessly.

The following link might be of some use.

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

Update: Link no longer works.


What version of the JDK/JRE are you using?

Also you might try ArciMath BigDecimal to see if theirs speeds it up for you.

Edit:

I remember reading somewhere (I think it was Effective Java) that the BigDecmal class was changed from being JNI called to a C library to all Java at some point... and it got faster from that. So it could be that any arbitrary precision library you use is not going to get you the speed you need.


Personally, I don't think BigDecimal is ideal for this.

You really want to implement your own Money class using longs internally to represent the smallest unit (i.e. cent, 10th cent). There is some work in that, implementing add() and divide() etc, but it's not really that hard.


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

Throwing a bit more hardware at this might be cheaper (considering the probability of having a currency calculation error).


1/b is not exactly representable with BigDecimal either. See the API docs to work out how the result is rounded.

It shouldn't be too difficult to write your own fixed decimal class based around a long field or two. I don't know any appropriate off the shelf libraries.


I know that I'm posting under very old topic, but this was the first topic found by google. Consider moving your calculations to the database from which you probably are taking the data for processing. Also I agree with Gareth Davis who wrote:

. In most bog standard webapps the overhead of jdbc access and accessing other network resources swamps any benefit of having really quick math.

In most cases wrong queries have higher impact on performance than math library.


Can you provide more insight as to the purpose of the calculation?

What your dealing with is a trade-off between speed and precision. How great will the loss in precision be if you switched to a primitive?

I think in some cases the user may be comfortable with less accuracy in exchange for speed, so long as they can hone in on the accurate calculation when needed. It really depends on what you will use this calculation for.

Perhaps you can allow the user to preview the result quickly using doubles, and then request the more precise value using BigDecimal if they wish?


Is JNI a possibility? You may be able to recover some speed and potentially leverage existing native fixed point libraries (maybe even some SSE* goodness too)

Perhaps http://gmplib.org/


Maybe you should look into getting hardware accelerated decimal arithmetics?

http://speleotrove.com/decimal/


Had a similar problem to this in an equity trading system back in 99. At the very start of the design we choose to have every number in the system represented as a long multiplied by 1000000 thus 1.3423 was 1342300L. But the main driver for this was memory foot print rather than straight line performance.

One word on caution, I wouldn't do this again today unless I was really sure that the math performance was super critical. In most bog standard webapps the overhead of jdbc access and accessing other network resources swamps any benefit of having really quick math.


It seems like the simplest solution is to use BigInteger instead of long to implement pesto's solution. If it seems messy it would be easy to write a class that wraps BigInteger to hide the precision adjustment.


easy... round your results often will eliminate double data type's error. if you are doing balance calculation, you have to also consider who will own the more/less penny caused by rounding.

bigdeciaml calculation produces more/less penny too, consider 100/3 case.


Commons Math - The Apache Commons Mathematics Library

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

According to my own benchmarking for my specific use case it's 10 - 20x slower than double (much better than 1000x) - basically for addition / multiplication. After benchmarking another algorithm which had a sequence of additions followed by an exponentiation the performance decrease was quite a bit worse: 200x - 400x. So it seems pretty fast for + and *, but not exp and log.

Commons Math는 Java 프로그래밍 언어 또는 Commons Lang에서 사용할 수없는 가장 일반적인 문제를 해결하는 가볍고 독립적 인 수학 및 통계 구성 요소 라이브러리입니다.

참고 : API는 생성자를 보호하여 팩토리 DfpField의 이름을 지정하는 동안 팩토리 패턴을 강제합니다 (보다 직관적 인 DfpFac 또는 DfpFactory가 아님). 그래서 당신은 사용해야합니다

new DfpField(numberOfDigits).newDfp(myNormalNumber)

Dfp를 인스턴스화하려면 여기에서 .multiply또는 무엇이든 호출 할 수 있습니다 . 좀 헷갈 리기 때문에 언급하겠다고 생각했습니다.


64 비트 JVM에서 아래와 같이 BigDecimal을 생성하면 약 5 배 더 빨라집니다.

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

참고 URL : https://stackoverflow.com/questions/611732/what-to-do-with-java-bigdecimal-performance

반응형