如何处理Java BigDecimal的性能?
我写生活的货币交易应用程序,所以我不得不使用货币值(这是一个遗憾的是,Java仍然没有十进制浮点types,没有任何支持任意精度的货币计算)。 “使用BigDecimal!” – 你可能会说。 我做。 但是现在我有一些代码,性能是一个问题,BigDecimal比double
基元慢1000倍以上(!)。
计算非常简单:系统所做的是多次计算a = (1/b) * c
(其中a
, b
和c
是定点值)。 但问题在于(1/b)
。 由于没有固定点,我不能使用定点算术。 BigDecimal result = a.multiply(BigDecimal.ONE.divide(b).multiply(c)
不仅丑陋,而且缓慢。
我可以用什么来取代BigDecimal? 我需要至less10倍的性能提升。 我发现其他优秀的JScience库具有任意精度算术,但它甚至比BigDecimal慢。
有什么build议么?
也许你应该开始用a = c / b来代替a =(1 / b)* c? 这不是10倍,但仍然是一些东西。
如果我是你,我会创造自己的class级钱,这将保持长期美元和长期分,并做math。
大多数双重操作给你足够的精度。 你可以用分数来表示10万亿美元的分值,这对你来说可能绰绰有余。
在我所从事的所有交易系统(四家不同的银行)中,他们已经使用了双倍的合适的四舍五入。 我没有看到有任何理由使用BigDecimal。
所以我原来的答案是错的,因为我的基准写得不好。 我想我是应该被批评的人,而不是OP;)这可能是我写过的第一个基准之一……哦,那就是你如何学习。 而不是删除答案,这里是我没有衡量错误的结果。 一些说明:
- 预先计算数组,所以我不会因生成结果而弄乱结果
- 不要调用
BigDecimal.doubleValue()
,因为它非常慢 - 不要
BigDecimal
添加BigDecimal
的结果。 只要返回一个值,并使用if语句来防止编译器优化。 尽pipe如此,确保它在大部分时间都能正常工作,以允许分支预测消除这部分代码。
testing:
- BigDecimal:完全按照您的build议进行math运算
- BigDecNoRecip:(1 / b)* c = c / b,只是做c / b
- 双:用双打math
这是输出:
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]); } }
假设你可以工作到一些任意但已知的精度(比如十亿分之一美分),并且有一个已知的最大值,你需要处理(万亿美元?),你可以编写一个类,把这个值存储为整数十亿分之一一分钱 你需要两个长的代表它。 这应该是使用双倍速度的十倍。 大约是BigDecimal的百倍。
大多数操作只是在每个部分执行操作并重新正规化。 分部稍微复杂一点,但并不多。
编辑:在回应评论。 你将需要在你的课堂上实现一个移位操作(容易,因为高的乘数是2的乘方)。 除数转换除数,直到不比分红还大; 从被除数中减去移位的除数,然后递增结果(适当的移位)。 重复。
再次编辑:您可能会发现BigInteger在这里做你所需要的。
按照美分的数量存放多头。 例如, BigDecimal money = new BigDecimal ("4.20")
变long money = 420
。 你只需要记住修改100来获得美元和美分的输出。 如果你需要跟踪,比如说,十分之一美分,那么它将变成long money = 4200
。
你可能想要移动到定点math。 只是现在search一些图书馆。 在sourceforge 定点上我还没有深入的看过这个。 beartonics
你用org.jscience.economics.moneytesting了吗? 因为这确保了准确性。 固定点将只与分配给每个块的位数一样精确,但速度很快。
我记得参加IBM的一个销售演示文稿,介绍BigDecimal的硬件加速实现。 因此,如果您的目标平台是IBM System z或System p,则可以无缝地利用此平台。
以下链接可能有一些用处。
servers/enable/site/education/wp/181ee/181ee.html
更新:链接不再起作用。
你使用的是什么版本的JDK / JRE?
你也可以试试ArciMath BigDecimal来看看他们是否为你加速。
编辑:
我记得在某处读过(我认为它是Effective Java),BigDecmal类从JNI调用到C库,到某个时候所有的Java …都变得更快了。 所以可能是因为你使用的任何一个精确的图书馆都不会让你获得所需的速度。
就个人而言,我不认为BigDecimal是理想的。
你真的想实现自己的货币类在内部使用多头代表最小的单位(即分,10美分)。 在这方面有一些工作,实现add()
和divide()
等,但并不是那么难。
Only 10x performance increase desired for something that is 1000x slower than primitive?!.
在此投入更多的硬件可能会更便宜(考虑到货币计算错误的可能性)。
1 / b也不能完全用BigDecimal表示。 查看API文档以了解结果如何四舍五入。
基于一两个长的字段编写自己的固定十进制类应该不会太困难。 我不知道任何适当的现成的图书馆。
我知道我在非常老的主题下发帖,但这是谷歌发现的第一个主题。 考虑将您的计算移动到您可能正在处理数据的数据库中。 我同意加雷思·戴维斯(Gareth Davis)写道:
。 在大多数标准的web应用程序中,jdbc访问和访问其他networking资源的开销大大提高了math的快速性。
在大多数情况下,错误的查询比math库对性能的影响更大。
你能提供更多的洞察力,以计算的目的?
你处理的是速度和精度之间的平衡。 如果切换到原始状态,精度的损失有多大?
我认为,在某些情况下,用户可能会感到用速度换算精度较低,只要能够在需要的时候精确计算即可。 这真的取决于你将使用这个计算。
也许你可以允许用户使用双击快速预览结果,如果他们愿意,可以使用BigDecimal请求更精确的值。
JNI是否有可能? 您可能能够恢复一些速度,并可能利用现有的本地固定点库(甚至可能是某些SSE *善良)
也许你应该看看硬件加速十进制算术?
在99年的股票交易系统中也遇到类似的问题。在devise之初,我们select将系统中的每一个数字都表示为一个长整数乘以1000000,因此1.3423是1342300L。 但是这个主要驱动因素是记忆足迹而不是直线performance。
一个谨慎的话,除非我真的确信mathperformance是超级重要的,否则今天我不会再这样做。 在大多数标准的web应用程序中,jdbc访问和访问其他networking资源的开销大大增加了math运算的快速性。
看起来最简单的解决scheme是使用BigInteger而不是很长的时间来实现pesto的解决scheme。 如果看起来很乱,那么编写一个封装BigInteger来隐藏精度调整的类将会很容易。
容易…结果往往会消除双数据types的错误。 如果你正在做平衡计算,你还必须考虑谁将拥有四舍五入造成的更多/更less的钱。
bigdeciaml计算也会产生更多/更less的分数,考虑100/3的情况。
Commons Math – Apache Commonsmath图书馆
http://mvnrepository.com/artifact/org.apache.commons/commons-math3/3.2
根据我自己的具体使用情况的基准testing,它的速度比双倍速度慢10-20倍(好于1000倍) – 基本上用于加法/乘法。 在对另一个algorithm进行基准testing后,得到一系列的加法运算,然后进行一个幂运算,性能下降比较差一些:200x – 400x。 所以+和*似乎相当快,但不是exp和log。
Commons Math是一个轻量级,自包含的math和统计组件的库,用于解决Java编程语言或Commons Lang中不可用的最常见问题。
注意:API保护构造函数强制工厂模式,同时命名工厂DfpField(而不是更直观的DfpFac或DfpFactory)。 所以你必须使用
new DfpField(numberOfDigits).newDfp(myNormalNumber)
要实例化一个Dfp,那么你可以调用.multiply
或其他什么。 我想我会提到这个,因为它有点混乱。
在64位JVM上创build你的BigDecimal,使其速度提高了5倍:
BigDecimal bd = new BigDecimal(Double.toString(d), MathContext.DECIMAL64);