2012-08-06 53 views
15

我在Clojure中实现了一些基本的复数运算,并且发现它比大致相当的Java代码慢10倍左右,即使是类型提示也是如此。Clojure中的快速复数运算

比较:

(defn plus [[^double x1 ^double y1] [^double x2 ^double y2]] 
    [(+ x1 x2) (+ y1 y2)]) 

(defn times [[^double x1 ^double y1] [^double x2 ^double y2]] 
    [(- (* x1 x2) (* y1 y2)) (+ (* x1 y2) (* y1 x2))]) 

(time (dorun (repeatedly 100000 #(plus [1 0] [0 1])))) 
(time (dorun (repeatedly 100000 #(times [1 0] [0 1])))) 

输出:

"Elapsed time: 69.429796 msecs" 
"Elapsed time: 72.232479 msecs" 

有:

public static void main(String[] args) { 
    double[] z1 = new double[] { 1, 0 }; 
    double[] z2 = new double[] { 0, 1 }; 
    double[] z3 = null; 

    long l_StartTimeMillis = System.currentTimeMillis(); 
    for (int i = 0; i < 100000; i++) { 
    z3 = plus(z1, z2); // assign result to dummy var to stop compiler from optimising the loop away 
    } 
    long l_EndTimeMillis = System.currentTimeMillis(); 
    long l_TimeTakenMillis = l_EndTimeMillis - l_StartTimeMillis; 
    System.out.format("Time taken: %d millis\n", l_TimeTakenMillis); 


    l_StartTimeMillis = System.currentTimeMillis(); 
    for (int i = 0; i < 100000; i++) { 
    z3 = times(z1, z2); 
    } 
    l_EndTimeMillis = System.currentTimeMillis(); 
    l_TimeTakenMillis = l_EndTimeMillis - l_StartTimeMillis; 
    System.out.format("Time taken: %d millis\n", l_TimeTakenMillis); 

    doNothing(z3); 
} 

private static void doNothing(double[] z) { 

} 

public static double[] plus (double[] z1, double[] z2) { 
    return new double[] { z1[0] + z2[0], z1[1] + z2[1] }; 
} 

public static double[] times (double[] z1, double[] z2) { 
    return new double[] { z1[0]*z2[0] - z1[1]*z2[1], z1[0]*z2[1] + z1[1]*z2[0] }; 
} 

输出:

Time taken: 6 millis 
Time taken: 6 millis 

事实上,类型提示似乎没有区别:如果我删除它们,我会得到大致相同的结果。什么是真正奇怪的是,如果我运行Clojure的脚本没有一个REPL,我会变慢结果:

"Elapsed time: 137.337782 msecs" 
"Elapsed time: 214.213993 msecs" 

所以我的问题是:我怎么能获得接近Java代码的性能?那么为什么在没有REPL的情况下运行clojure时,表达式需要更长时间来评估?

UPDATE ==============

大,使用deftype与在deftype类型提示,并在defn s,而使用dotimes而不是repeatedly给出的性能一样好或者比Java版本更好。感谢你们俩。

(deftype complex [^double real ^double imag]) 

(defn plus [^complex z1 ^complex z2] 
    (let [x1 (double (.real z1)) 
     y1 (double (.imag z1)) 
     x2 (double (.real z2)) 
     y2 (double (.imag z2))] 
    (complex. (+ x1 x2) (+ y1 y2)))) 

(defn times [^complex z1 ^complex z2] 
    (let [x1 (double (.real z1)) 
     y1 (double (.imag z1)) 
     x2 (double (.real z2)) 
     y2 (double (.imag z2))] 
    (complex. (- (* x1 x2) (* y1 y2)) (+ (* x1 y2) (* y1 x2))))) 

(println "Warm up") 
(time (dorun (repeatedly 100000 #(plus (complex. 1 0) (complex. 0 1))))) 
(time (dorun (repeatedly 100000 #(times (complex. 1 0) (complex. 0 1))))) 
(time (dorun (repeatedly 100000 #(plus (complex. 1 0) (complex. 0 1))))) 
(time (dorun (repeatedly 100000 #(times (complex. 1 0) (complex. 0 1))))) 
(time (dorun (repeatedly 100000 #(plus (complex. 1 0) (complex. 0 1))))) 
(time (dorun (repeatedly 100000 #(times (complex. 1 0) (complex. 0 1))))) 

(println "Try with dorun") 
(time (dorun (repeatedly 100000 #(plus (complex. 1 0) (complex. 0 1))))) 
(time (dorun (repeatedly 100000 #(times (complex. 1 0) (complex. 0 1))))) 

(println "Try with dotimes") 
(time (dotimes [_ 100000] 
     (plus (complex. 1 0) (complex. 0 1)))) 

(time (dotimes [_ 100000] 
     (times (complex. 1 0) (complex. 0 1)))) 

输出:

Warm up 
"Elapsed time: 92.805664 msecs" 
"Elapsed time: 164.929421 msecs" 
"Elapsed time: 23.799012 msecs" 
"Elapsed time: 32.841624 msecs" 
"Elapsed time: 20.886101 msecs" 
"Elapsed time: 18.872783 msecs" 
Try with dorun 
"Elapsed time: 19.238403 msecs" 
"Elapsed time: 17.856938 msecs" 
Try with dotimes 
"Elapsed time: 5.165658 msecs" 
"Elapsed time: 5.209027 msecs" 
+0

您是否尝试过设置['*警告上反射*'](http://clojuredocs.org/clojure_core /clojure.core/*warn-on-reflection*)查看是否有任何反射潜入? – DaoWen 2012-08-06 08:46:53

+0

@DaoWen:不,我从来没有使用过这种设置。我刚刚用'(set!* warn-on-reflection * true)'在脚本的顶部再次运行脚本,并且没有向stdout输出警告,所以这意味着没有反射被使用,对吧?只是想确保我正确使用它。 – OpenSauce 2012-08-06 09:46:56

回答

22

您的性能下降的可能的原因是:

  • Clojure的载体本质上比Java双[]数组更重量级的数据结构。所以你在创建和读取矢量时有相当多的额外开销。
  • 你是拳击双打作为你的功能的参数,当他们被放入向量。这种低级数字代码中的拳击/拆箱比较昂贵。
  • 类型提示(^double)没有帮助你:虽然你可以在正常的Clojure函数上有原始类型提示,但它们不能用于向量。

有关更多详细信息,请参阅此blog post on accelerating primitive arithmetic

如果你真的想用Clojure快复数,你可能需要使用deftype,像实现它们:

(deftype Complex [^double real ^double imag]) 

,然后定义使用这种类型的所有复杂的功能。这将使您能够在整个过程中使用基本算术,并且应该大致等于编写良好的Java代码的性能。

+0

我认为[defrecord](http://clojuredocs.org/clojure_core/clojure.core/defrecord)比[deftype](http://clojuredocs.org/clojure_core/clojure.core/deftype)适合简单的类型,比如这个。 – DaoWen 2012-08-06 08:55:54

+1

@DaoWen - 我可能是错的,但我相信你会从deftype中获得更好的性能 - 它比defrecord的开销(略)少。 defrecord实现完全类似于map的行为,更适合于“业务对象数据”,而deftype更适合稍低级别的数据类型。 – mikera 2012-08-06 09:25:16

+0

谢谢,我想知道deftype/defrecord,但认为它们可能会引入更多开销,但我会尝试deftype(以及该博文中的内容)并报告回来。 – OpenSauce 2012-08-06 09:45:14

4
  • 我不知道很多关于基准测试,但似乎你需要 当你开始测试热身JVM。所以当你在REPL中做它已经热身了。当你以脚本运行时,它还没有。

  • 在java中,你运行1个方法内的所有循环。除plustimes之外没有其他方法被调用。在clojure中,您创建匿名函数并重复调用以调用它。这需要一些时间。您可以用dotimes替换它。

我尝试:

(println "Warm up") 
(time (dorun (repeatedly 100000 #(plus [1 0] [0 1])))) 
(time (dorun (repeatedly 100000 #(times [1 0] [0 1])))) 
(time (dorun (repeatedly 100000 #(plus [1 0] [0 1])))) 
(time (dorun (repeatedly 100000 #(times [1 0] [0 1])))) 
(time (dorun (repeatedly 100000 #(plus [1 0] [0 1])))) 
(time (dorun (repeatedly 100000 #(times [1 0] [0 1])))) 

(println "Try with dorun") 
(time (dorun (repeatedly 100000 #(plus [1 0] [0 1])))) 
(time (dorun (repeatedly 100000 #(times [1 0] [0 1])))) 

(println "Try with dotimes") 
(time (dotimes [_ 100000] 
     (plus [1 0] [0 1]))) 

(time (dotimes [_ 100000] 
     (times [1 0] [0 1]))) 

结果:

Warm up 
"Elapsed time: 367.569195 msecs" 
"Elapsed time: 493.547628 msecs" 
"Elapsed time: 116.832979 msecs" 
"Elapsed time: 46.862176 msecs" 
"Elapsed time: 27.805174 msecs" 
"Elapsed time: 28.584179 msecs" 
Try with dorun 
"Elapsed time: 26.540489 msecs" 
"Elapsed time: 27.64626 msecs" 
Try with dotimes 
"Elapsed time: 7.3792 msecs" 
"Elapsed time: 5.940705 msecs" 
+0

谢谢,这很有道理。我得到了类似的结果。 – OpenSauce 2012-08-06 09:40:28