Reputation: 661
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 support arbitrary-precision monetary calculations). "Use BigDecimal!" — you might say. I do. But now I have some code where performance is an issue, and BigDecimal is more than 1000 times (!) slower than double
primitives.
The calculations are very simple: what the system does is calculating a = (1/b) * c
many many times (where a
, b
and c
are fixed-point values). The problem, however, lies with this (1/b)
. I can't use fixed point arithmetic because there is no fixed point. And BigDecimal result = a.multiply(BigDecimal.ONE.divide(b).multiply(c)
is not only ugly, but sluggishly slow.
What can I use to replace BigDecimal? I need at least 10x performance increase. I found otherwise excellent JScience library which has arbitrary-precision arithmetics, but it's even slower than BigDecimal.
Any suggestions?
Upvotes: 66
Views: 48490
Reputation: 21
I know this is a really old thread, but i am writing an app (incidentally a trading app), in which computation of the indicators like MACD (which computes multiple exponential moving averages) over several thousand ticks of historical candlesticks was taking an unacceptable amount of time (several minutes). I was using BigDecimal.
every time i scrolled or resized the window, it would have to just iterate through the cached values to resize the Y scale, but even that would take several seconds to update. it made the app unusable. every time i would tweak the parameters for various indicators, it would take several minutes again to recompute.
then i switched it all to double and it's sooooo much faster. the problem was that i cache values using a hashmap. the solution i came up with uses a pool of wrappers for the double values. by pooling the wrappers, you don't take the performance hit of autoboxing to/from Double.
the app now calculates MACD (+MACD signal, MACD histogram) instantly with no lag. it's amazing how expensive BigDecimal object creation was. think about something like a.add( b.multiply( c )).scale(3) and how many objects that one statement creates.
import java.util.HashMap;
public class FastDoubleMap<K>
{
private static final Pool<Wrapper> POOL = new Pool<FastDoubleMap.Wrapper>()
{
protected Wrapper newInstance()
{
return new Wrapper();
}
};
private final HashMap<K, Wrapper> mMap;
public FastDoubleMap()
{
mMap = new HashMap<>();
}
public void put( K pKey, double pValue )
{
Wrapper lWrapper = POOL.checkOut();
lWrapper.mValue = pValue;
mMap.put( pKey, lWrapper );
}
public double get( K pKey )
{
Wrapper lWrapper = mMap.get( pKey );
if( lWrapper == null )
{
return Double.NaN;
}
else
{
return lWrapper.mValue;
}
}
public double remove( K pKey )
{
Wrapper lWrapper = mMap.remove( pKey );
if( lWrapper != null )
{
double lDouble = lWrapper.mDouble;
POOL.checkIn( lWrapper );
return lDouble;
}
else
{
return Double.NaN;
}
}
private static class Wrapper
implements Pooled
{
private double mValue ;
public void cleanup()
{
mValue = Double.NaN;
}
}
}
Upvotes: 0
Reputation: 119
On a 64bit JVM creating your BigDecimal as below makes it about 5x faster:
BigDecimal bd = new BigDecimal(Double.toString(d), MathContext.DECIMAL64);
Upvotes: 2
Reputation: 32343
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:
BigDecimal.doubleValue()
, as it's extremely slowBigDecimal
s. 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:
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]);
}
}
Upvotes: 16
Reputation: 31543
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 is a library of lightweight, self-contained mathematics and statistics components addressing the most common problems not available in the Java programming language or Commons Lang.
Note: The API protects the constructors to force a factory pattern while naming the factory DfpField (rather than the somewhat more intuitive DfpFac or DfpFactory). So you have to use
new DfpField(numberOfDigits).newDfp(myNormalNumber)
to instantiate a Dfp, then you can call .multiply
or whatever on this. I thought I'd mention this because it's a bit confusing.
Upvotes: 1
Reputation: 26
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.
Upvotes: 1
Reputation: 29
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.
Upvotes: 0
Reputation: 311
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.
Upvotes: 0
Reputation: 26876
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.
Upvotes: 9
Reputation: 8536
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).
Upvotes: 2
Reputation: 533750
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.
Upvotes: 20
Reputation: 28069
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.
Upvotes: 0
Reputation: 17307
Maybe you should look into getting hardware accelerated decimal arithmetics?
http://speleotrove.com/decimal/
Upvotes: 0
Reputation: 30014
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/
Upvotes: 0
Reputation: 59428
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.
Upvotes: 3
Reputation: 61536
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.
Upvotes: 2
Reputation: 4778
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.
Upvotes: 5
Reputation: 147154
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.
Upvotes: 1
Reputation: 23901
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.
Upvotes: 5
Reputation: 18336
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.
Upvotes: 39