我在Clojure中总结了一长串比率,如:
(defn sum-ratios
[n]
(reduce
(fn [total ind]
(+
total
(/
(inc (rand-int 100))
(inc (rand-int 100)))))
(range 0 n)))
各种n的运行时间是:
(不太精确)替代方案是将这些值加总为双精度:
(defn sum-doubles
[n]
(reduce
(fn [total ind]
(+
total
(double
(/
(inc (rand-int 100))
(inc (rand-int 100))))))
(range 0 n)))
此版本的运行时为:
为什么加总比率要慢得多?我猜测它与找到Ratios的分母的最小公倍数有关,但是有没有人知道Clojure用什么算法来比较Ratios?
答案 0 :(得分:13)
让我们关注+
两个Ratio
时发生的情况,这是减少中每一步所发生的情况。我们从the two-arity version of +开始:
([x y] (. clojure.lang.Numbers (add x y)))
这会将我们带到Numbers.add(Obj, Obj)
:
return ops(x).combine(ops(y)).add((Number)x, (Number)y);
ops
looks at the class of the first operand并找到RatioOps
。这导致了RatioOps.add
功能:
final public Number add(Number x, Number y){
Ratio rx = toRatio(x);
Ratio ry = toRatio(y);
Number ret = divide(ry.numerator.multiply(rx.denominator)
.add(rx.numerator.multiply(ry.denominator))
, ry.denominator.multiply(rx.denominator));
return normalizeRet(ret, x, y);
}
所以这是你的算法。此处有五个 BigInteger
个操作(三个乘法,一个加法,一个除法):
(yn*xd + xn*yd) / (xd*yd)
您可以看到multiply的实施方式;它本身并不是微不足道的,你可以自己检查其他人。
果然,divide function涉及在两个数字之间找到gcd
,以便可以减少:{/ p>
static public Number divide(BigInteger n, BigInteger d){
if(d.equals(BigInteger.ZERO))
throw new ArithmeticException("Divide by zero");
BigInteger gcd = n.gcd(d);
if(gcd.equals(BigInteger.ZERO))
return BigInt.ZERO;
n = n.divide(gcd);
d = d.divide(gcd);
...
}
gcd
function会创建两个新的MutableBigInteger
个对象。
从计算上来说,它很昂贵,正如你从上面所能看到的那样。但是,不要忽视额外附带对象创建的成本(如上面gcd
所述),因为我们涉及非缓存内存访问通常会更加昂贵。
double
转换不是免费的,FWIW,it involves a division of two newly-created BigDecimal
s。
您真的需要一个分析器才能看到完全的成本。但希望上面给出了一点背景。