我偶然发现了性能基准测试在这里,我觉得很奇怪为什么 Clojure 的执行速度会被 Java 打败。
所以我将它扔进了分析器(在修改他们版本使用非检查数学后 - 这并没有帮助)但是没有显示出来。嗯。反编译并发现
// Decompiling class: leibniz$calc_pi_leibniz
import clojure.lang.*;
public final class leibniz$calc_pi_leibniz extends AFunction implements LD
{
public static double invokeStatic(final long rounds) {
final long end = 2L + rounds;
long i = 2L;
double x = 1.0;
double pi = 1.0;
while (i != end) {
final double x2 = -x;
final long n = i + 1L;
final double n2 = x2;
pi += Numbers.divide(x2, 2L * i - 1L);
x = n2;
i = n;
}
return Numbers.unchecked_multiply(4L, pi);
}
@Override
public Object invoke(final Object o) {
return invokeStatic(RT.uncheckedLongCast(o));
}
@Override
public final double invokePrim(final long rounds) {
return invokeStatic(rounds);
}
}
所以看起来整数/长类型边界至少花费我们一个方法查找,可能在 Numbers.divide 中?
所以我就将所有内容强制转换为双精度浮点数(甚至我们的索引变量)
(def rounds 100000000)
(defn calc-pi-leibniz2
"Eliminate mixing of long/double to avoid clojure.numbers invocations."
^double
[^long rounds]
(let [end (+ 2.0 rounds)]
(loop [i 2.0 x 1.0 pi 1.0]
(if (= i end)
(* 4.0 pi)
(let [x (- x)]
(recur (inc i) x (+ pi (/ x (dec (* 2 i))))))))))
leibniz=> (c/quick-bench (calc-pi-leibniz rounds))
Evaluation count : 6 in 6 samples of 1 calls.
Execution time mean : 575.352216 ms
Execution time std-deviation : 10.070268 ms
Execution time lower quantile : 566.210399 ms ( 2.5%)
Execution time upper quantile : 588.772187 ms (97.5%)
Overhead used : 1.884700 ns
nil
leibniz=> (c/quick-bench (calc-pi-leibniz2 rounds))
Evaluation count : 6 in 6 samples of 1 calls.
Execution time mean : 158.509049 ms
Execution time std-deviation : 759.113165 ╡s
Execution time lower quantile : 157.234899 ms ( 2.5%)
Execution time upper quantile : 159.205374 ms (97.5%)
Overhead used : 1.884700 ns
nil
有什么想法吗?为什么Java 实现在除法时分担相同的惩罚?[两个版本都是使用 unchecked-math 实现,并在 :warn-on-boxed 模式下被实现]。
我还尝试了一个使用 fastmath 原始数学运算符的变体,实际上变得更慢了。到目前为止,没有任何方法能打败将循环索引 i
强制转换为双精度浮点数(这通常是我不会做的事情)。