2012-08-06 36 views
15

Tôi đã triển khai một số số học phức tạp cơ bản trong Clojure và nhận thấy rằng nó chậm hơn khoảng 10 lần so với mã Java tương đương, ngay cả với các gợi ý kiểu.Số học phức tạp nhanh ở Clojure

Hãy so sánh:

(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])))) 

đầu ra:

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

với:

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] }; 
} 

đầu ra:

Time taken: 6 millis 
Time taken: 6 millis 

Trên thực tế, các gợi ý kiểu dường như không tạo ra sự khác biệt: nếu tôi loại bỏ chúng, tôi nhận được kết quả tương tự. Điều thực sự kỳ lạ là nếu tôi chạy kịch bản Clojure mà không một REPL, tôi nhận được kết quả chậm hơn:

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

Vì vậy, câu hỏi của tôi là: làm thế nào tôi có thể nhận được gần đến việc thực hiện các mã Java? Và tại sao Earth lại làm các biểu thức mất nhiều thời gian hơn để đánh giá khi chạy clojure mà không có REPL?

CẬP NHẬT ==============

Tuyệt vời, sử dụng deftype với kiểu gợi ý trong deftype và trong defn s, và sử dụng dotimes hơn repeatedly cho hiệu suất tốt như hoặc tốt hơn phiên bản Java. Cám ơn hai bạn.

(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)))) 

Output:

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

Các bạn đã cố gắng thiết lập [ '* cảnh báo-on-phản ánh *'] (http://clojuredocs.org/clojure_core /clojure.core/*warn-on-reflection*) để xem liệu có bất kỳ sự phản chiếu nào lén lút vào không? – DaoWen

+0

@DaoWen: không, tôi chưa bao giờ sử dụng cài đặt đó. Tôi vừa chạy kịch bản lệnh một lần nữa với '(set! * Warn-on-reflection * true)' ở trên cùng của nó, và không có cảnh báo nào được in để stdout, vậy có nghĩa là không có sự phản chiếu nào được sử dụng, đúng không? Chỉ muốn chắc chắn rằng tôi đang sử dụng nó một cách chính xác. – OpenSauce

Trả lời

22

Những lý do có khả năng cho hiệu suất chậm của bạn là:

  • Clojure vectơ là bản chất cấu trúc dữ liệu nặng hơn gấp đôi mảng Java []. Vì vậy, bạn có khá nhiều chi phí phụ trong việc tạo và đọc vectơ.
  • Bạn là quyền anh đôi làm đối số cho các chức năng của bạn và cũng có thể khi chúng được đưa vào vectơ. Quyền anh/unboxing tương đối đắt trong loại mã số cấp thấp này.
  • Các gợi ý loại (^double) không giúp bạn: trong khi bạn có thể có các gợi ý kiểu nguyên thủy đối với các hàm Clojure thông thường, chúng sẽ không hoạt động trên vectơ.

Xem này blog post on accelerating primitive arithmetic để biết thêm chi tiết.

Nếu bạn thực sự muốn số phức nhanh trong Clojure, bạn có thể sẽ cần phải thực hiện chúng bằng cách sử deftype, một cái gì đó như:

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

Và sau đó xác định tất cả các chức năng phức tạp bằng cách sử dụng loại này. Điều này sẽ cho phép bạn sử dụng số học nguyên thủy trong suốt, và tương đương với hiệu suất của mã Java được viết tốt.

+0

Tôi nghĩ [defrecord] (http://clojuredocs.org/clojure_core/clojure.core/defrecord) được đề xuất trên [deftype] (http://clojuredocs.org/clojure_core/clojure.core/deftype) đối với các loại đơn giản như điều này. – DaoWen

+1

@DaoWen - Tôi có thể sai nhưng tôi tin rằng bạn sẽ nhận được hiệu suất tốt hơn từ deftype - nó có (hơi) ít chi phí hơn so với defrecord. defrecord thực hiện đầy đủ hành vi giống như bản đồ và phù hợp hơn cho "dữ liệu đối tượng nghiệp vụ" trong khi deftype phù hợp hơn với các loại dữ liệu cấp thấp hơn một chút. – mikera

+0

Cảm ơn, tôi tự hỏi về deftype/defrecord nhưng nghĩ rằng họ có thể giới thiệu thêm chi phí, nhưng tôi sẽ cung cấp cho deftype một thử (và các công cụ trong bài đăng blog) và báo cáo lại. – OpenSauce

4
  • Tôi không biết nhiều về kiểm tra điểm chuẩn nhưng có vẻ như bạn cần để khởi động jvm khi bạn bắt đầu kiểm tra. Vì vậy, khi bạn làm điều đó trong REPL nó đã được làm ấm lên. Khi bạn chạy như kịch bản nó chưa được.

  • Trong java, bạn chạy tất cả các vòng trong phương thức 1. Không có phương pháp nào khác ngoại trừ plustimes được gọi. Trong clojure bạn tạo ra chức năng nặc danh và gọi liên tục để gọi nó. Phải mất một thời gian. Bạn có thể thay thế bằng dotimes.

thử của tôi:

(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]))) 

Kết quả:

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

Cảm ơn, điều đó có ý nghĩa. Tôi nhận được kết quả tương tự. – OpenSauce

Các vấn đề liên quan