【发布时间】:2017-11-15 18:25:05
【问题描述】:
我很喜欢Frank Nelson Cole 的故事,他在 1903 年的一场著名的“无言讲座”中展示了 2^67 - 1 的素因数分解。如今,使用以下简单算法可以很容易地找到因式分解:
(def mersenne67 (dec (expt 2 67)))
(->> (iterate inc 2)
(filter #(zero? (rem mersenne67 %)))
(first))
但是,我注意到这个 Clojure 代码所用的时间大约是等效的 Java 或 Kotlin 代码的两倍。 (在我的机器上约 40 秒对约 20 秒)
这是我与之比较的 Java:
public static BigInteger mersenne() {
BigInteger mersenne67 =
BigInteger.valueOf(2).pow(67).subtract(BigInteger.ONE);
return Stream.iterate(BigInteger.valueOf(2), (x -> x.add(BigInteger.ONE)))
.filter(x -> mersenne67.remainder(x).equals(BigInteger.ZERO))
.findFirst()
.get();
}
在较低级别重写 Clojure 代码没有任何区别:
(def mersenne67 (-> (BigInteger/valueOf 2)
(.pow (BigInteger/valueOf 67))
(.subtract BigInteger/ONE)))
(->> (iterate #(.add ^BigInteger % BigInteger/ONE) (BigInteger/valueOf 2))
(filter #(= BigInteger/ZERO (.remainder ^BigInteger mersenne67 %)))
(first))
在使用 VisualVM 分析代码后,主要嫌疑人似乎是 clojure.lang.Iterate.first(),这几乎完全解释了这些函数运行时间的差异。 Java 的等效java.util.stream.ReferencePipeline.findFirst() 只运行一小部分时间(~22 对~2 秒)。
这引出了我的问题:Java(和 Kotlin)如何在这项任务上花费如此少的时间?
【问题讨论】:
-
什么是
expt? -
Clojure 的 numeric tower 中的求幂函数。
标签: java performance clojure