2012-03-12 50 views
4

我想知道是否有人可以幫助我在Clojure 1.3中執行此代碼片段。我試圖實現一個簡單的函數,它需要兩個向量並且完成一個產品的總和。Clojure 1.3中的函數性能

所以我們可以說的載體是X(大小10,000個元素)和B(大小爲3種的元素),和的乘積之和被存儲在向量Y,數學上它看起來像這樣:

Y0 = B0 * X2 + B1 * X1 + B2 * X0

Y1 = B0 * X3 + B1 * X2 + B2 * X1

Y2 = B0 * X4 + B1 * X3 + B2 * X2

等...

爲此例如,Y的大小最終爲9997,相當於(10,000 - 3)。我已經設置了接受任意大小的X和B的函數。

以下是代碼:它基本上每次從X中取出(count b)個元素,將其反轉,將*映射到B上並彙總結果序列的內容以產生Y.

(defn filt [b-vec x-vec] 
    (loop [n 0 sig x-vec result []] 
    (if (= n (- (count x-vec) (count b-vec))) 
     result 
     (recur (inc n) (rest sig) (conj result (->> sig 
                (take (count b-vec)) 
                (reverse) 
                (map * b-vec) 
                (apply +))))))) 

的元件在令X (vec (range 1 10001))和B是[1 2 3],該功能需要約6秒運行。我希望有人能夠提出對運行時間的改進,無論是算法,還是我可能會濫用的語言細節。

謝謝!

P.S.我做了(set! *warn-on-reflection* true),但沒有得到任何反射警告消息。

回答

7

您使用次數多次不必要。下面的代碼計算算一次只

(defn filt [b-vec x-vec] 
    (let [bc (count b-vec) xc (count x-vec)] 
    (loop [n 0 sig x-vec result []] 
     (if (= n (- xc bc)) 
      result 
      (recur (inc n) (rest sig) (conj result (->> sig 
                (take bc) 
                (reverse) 
                (map * b-vec) 
                (apply +)))))))) 


(time (def b (filt [1 2 3] (range 10000)))) 
=> "Elapsed time: 50.892536 msecs" 
+0

也許還能夠避免重複呼叫通過反轉B載體逆轉一次而不是X矢量的部分數百次?我不知道Clojure,但這看起來像很多額外的工作。 – 2012-03-12 04:42:42

+0

實際上,反向調用是從x-vec獲取的元素(即sig被反轉) – Ankur 2012-03-12 04:46:03

+0

如果x-vec的大小爲10000,那麼我們必須調用3333次調用來反轉這些3個元素我們正在採取,我們不是嗎?但是,如果我們只逆轉一次b-vec,我們應該能夠避免它們。或者可能是半夜,我可能需要睡覺。 – 2012-03-12 04:51:52

3

要遵循以ANKUR的出色答卷,你也可避免重複調用反向功能,這會讓我們甚至有點更多的性能。

(defn filt [b-vec x-vec] 
    (let [bc (count b-vec) xc (count x-vec) bb-vec (reverse b-vec)] 
    (loop [n 0 sig x-vec result []] 
     (if (= n (- xc bc)) 
      result 
      (recur (inc n) (rest sig) (conj result (->> sig 
                (take bc) 
                (map * bb-vec) 
                (apply +)))))))) 
6

如果你真的想要這種計算的最佳性能,你應該使用數組而不是向量。陣列具有許多性能優勢:

  • 他們支持O(1)索引的查找和寫入 - 稍微比爲O(log32 N)
  • 他們是可變的,所以你不需要載體更好始終構建新陣列 - 您可以創建一個陣列作爲輸出緩衝區
  • 它們在引擎蓋下表示爲Java陣列,因此受益於內置於JVM中的各種陣列優化
  • 您可以使用原始數組(例如Java的兩倍),這是速度遠遠超過如果您使用盒裝的對象數

代碼會是這樣的:

(defn filt [^doubles b-arr 
      ^doubles x-arr] 
    (let [bc (count b-arr) 
      xc (count x-arr) 
      rc (inc (- xc bc)) 
      result ^doubles (double-array rc)] 
     (dotimes [i rc] 
     (dotimes [j bc] 
      (aset result i (+ (aget result i) (* (aget x-arr (+ i j)) (aget b-arr j)))))) 
     result)) 
+0

所以我試了這個版本,並讓它運行。我將b-vec和x-vec轉換成如下這樣的Java數組:'(def b-arr(進入數組Double b-vec))和'(def x-arr(進入數組Double x-vec))' 。但是,這個版本的代碼比Ankur和Ian提供的優化版本運行速度慢得多。平均而言,我使用純功能版本獲得了大約25毫秒的時間,但使用Java陣列版本的時間大約爲850毫秒。有什麼建議麼? – endbegin 2012-03-12 18:44:05

+0

你會想爲'b-arr'和'x-arr'使用原始數組。嘗試'(def b-arr(double-array b-vec))' – mikera 2012-03-14 03:01:40

+0

好的,我想出了爲什麼這個本地Java版本很慢。我用'defn filt [^雙打b-arr ^雙打x-arr]'取代了'defn filt [b-arr x-arr]',它運行得非常快。感謝您的回答,希望我能將您的答案標記爲已接受。整個練習對我非常有幫助。 – endbegin 2012-03-14 03:04:40