What to do with Java BigDecimal performance?

后端 未结 20 2482
陌清茗
陌清茗 2020-11-29 17:28

I write currency trading applications for living, so I have to work with monetary values (it\'s a shame that Java still doesn\'t have decimal float type and has nothing to s

相关标签:
20条回答
  • 2020-11-29 18:07

    Most double operations give you more than enough precision. You can represent $10 trillion with cent accuracy with double which may be more than enough for you.

    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.

    0 讨论(0)
  • 2020-11-29 18:08

    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.

    0 讨论(0)
  • So my original answer was just flat out wrong, because my benchmark was written badly. I guess I'm the one who should have been criticized, not OP ;) This may have been one of the first benchmarks I ever wrote... oh well, that's how you learn. Rather than deleting the answer, here are the results where I'm not measuring the wrong thing. Some notes:

    • Precalculate the arrays so I don't mess with the results by generating them
    • Don't ever call BigDecimal.doubleValue(), as it's extremely slow
    • Don't mess with the results by adding BigDecimals. Just return one value, and use an if statement to prevent compiler optimization. Make sure to have it work most of the time to allow branch prediction to eliminate that part of the code, though.

    Tests:

    • BigDecimal: do the math exactly as you suggested it
    • BigDecNoRecip: (1/b) * c = c/b, just do c/b
    • Double: do the math with doubles

    Here is the output:

     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
    

    Here's the 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]);
      }
    }
    
    0 讨论(0)
  • 2020-11-29 18:09

    On a 64bit JVM creating your BigDecimal as below makes it about 5x faster:

    BigDecimal bd = new BigDecimal(Double.toString(d), MathContext.DECIMAL64);
    
    0 讨论(0)
  • 2020-11-29 18:10

    May be you should start with replacing a = (1/b) * c with a = c/b ? It's not 10x, but still something.

    If I were you, I'd create my own class Money, which would keep long dollars and long cents, and do math in it.

    0 讨论(0)
  • 2020-11-29 18:10

    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/

    0 讨论(0)
提交回复
热议问题