在我看来,与java数组相比,clojure向量的性能略有下降。因此,我认为“传统智慧”是对于代码中性能关键的部分,你最好使用java数组。
然而,我的测试表明情况并非如此:
Clojure 1.3.0
user=> (def x (vec (range 100000)))
#'user/x
user=> (def xa (int-array x))
#'user/xa
user=> (time (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ s (nth x i))) s)))
"Elapsed time: 16.551 msecs"
4999950000
user=> (time (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ s (aget xa i))) s)))
"Elapsed time: 1271.804 msecs"
4999950000
正如您所看到的,该年龄增加了大约800%的时间。这两种方法仍然比本机java慢:
public class Test {
public static void main (String[] args) {
int[] x = new int[100000];
for (int i=0;i<100000;i++) {
x[i]=i;
}
long s=0;
long end, start = System.nanoTime();
for (int i=0;i<100000;i++) {
s+= x[i];
}
end = System.nanoTime();
System.out.println((end-start)/1000000.0+" ms");
System.out.println(s);
}
}
> java Test
1.884 ms
4999950000
那么,我的结论应该是,aget比第n个慢80倍,比java中的[] -access慢约800倍吗?
答案 0 :(得分:9)
我怀疑这取决于aget函数对原始类型的反射和自动装箱....
幸运的是,aget / aset对原始数组有高效的重载,可避免反射,只需直接进行数组[i]访问(参见here和here)。
您只需传递一个类型提示即可选择正确的功能。
(type xa)
[I ; indicates array of primitive ints
; with type hint on array
;
(time (loop [i 0 s 0]
(if (< i 100000) (recur (inc i)
(+ s (aget ^ints xa i))) s)))
"Elapsed time: 6.79 msecs"
4999950000
; without type hinting
;
(time (loop [i 0 s 0]
(if (< i 100000) (recur (inc i)
(+ s (aget xa i))) s)))
"Elapsed time: 1135.097 msecs"
4999950000
答案 1 :(得分:4)
看起来反射正在消除所有测试的准确性:
user> (set! *warn-on-reflection* true)
true
user> (def x (vec (range 100000)))
#'user/x
user> (def xa (int-array x))
#'user/xa
user> (time (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ s (nth x i))) s)))
NO_SOURCE_FILE:1 recur arg for primitive local: s is not matching primitive, had: Object, needed: long
Auto-boxing loop arg: s
"Elapsed time: 12.11893 msecs"
4999950000
user> (time (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ s (aget xa i))) s)))
Reflection warning, NO_SOURCE_FILE:1 - call to aget can't be resolved.
NO_SOURCE_FILE:1 recur arg for primitive local: s is not matching primitive, had: Object, needed: long
Auto-boxing loop arg: s
Reflection warning, NO_SOURCE_FILE:1 - call to aget can't be resolved.
"Elapsed time: 2689.865468 msecs"
4999950000
user>
第二个碰巧有更多的反思。
运行此类基准测试时,请确保多次运行以使hotSpot编译器预热
user> (time (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ s (aget xa i))) (long s))))
"Elapsed time: 3135.651399 msecs"
4999950000
user> (time (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ (long s) (aget xa i))) (long s))))
"Elapsed time: 1014.218461 msecs"
4999950000
user> (time (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ (long s) (aget xa i))) (long s))))
"Elapsed time: 998.280869 msecs"
4999950000
user> (time (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ (long s) (aget xa i))) (long s))))
"Elapsed time: 970.17736 msecs"
4999950000
在这种情况下,几次运行将其降低到原始时间的1/3(尽管反射仍然是这里的主要问题)
如果我用dotimes加热它们,结果会有很大改善:
(dotimes [_ 1000] (time (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ s (nth x i))) s))))
"Elapsed time: 3.704714 msecs"
(dotimes [_ 1000] (time (loop [i 0 s 0] (if (< i 100000) (recur (inc i) (+ (long s) (aget xa i))) (long s)))))
"Elapsed time: 936.03987 msecs"
答案 2 :(得分:4)
似乎根本不需要任何类型的提示,Clojure可以很好地开箱即用。
当需要对集合执行polyadic函数时,只需使用apply和函数即可。如果需要将函数应用于集合中的元素并将结果存储在累加器中,请使用reduce。在这种情况下,两者都适用。
=> (def xa (into-array (range 100000)))
#'user/xa
=> (time (apply + xa))
"Elapsed time: 12.264753 msecs"
4999950000
=>(time (reduce + xa))
"Elapsed time: 2.735339 msecs"
4999950000
甚至更简单地平衡了这些差异,虽然比上述最佳情况略慢:
=> (def xa (range 100000))
#'user/xa
=> (time (apply + xa))
"Elapsed time: 4.547634 msecs"
4999950000
=> (time (reduce + xa))
"Elapsed time: 4.506572 msecs"
尝试尽可能编写最简单的代码,只有在速度不够快的情况下才进行优化。