2012-04-13 101 views
4

關於這個問題的後續關於aget performance奇怪的aget優化行爲

似乎有什麼東西很奇怪進行優化明智。我們知道下面是真實的:

=> (def xa (int-array (range 100000))) 
#'user/xa 

=> (set! *warn-on-reflection* true) 
true 

=> (time (reduce + (for [x xa] (aget ^ints xa x)))) 
"Elapsed time: 42.80174 msecs" 
4999950000 

=> (time (reduce + (for [x xa] (aget xa x)))) 
"Elapsed time: 2067.673859 msecs" 
4999950000 
Reflection warning, NO_SOURCE_PATH:1 - call to aget can't be resolved. 
Reflection warning, NO_SOURCE_PATH:1 - call to aget can't be resolved. 

然而,另外一些實驗確實weirded我出去:

=> (for [f [get nth aget]] (time (reduce + (for [x xa] (f xa x))))) 
("Elapsed time: 71.898128 msecs" 
"Elapsed time: 62.080851 msecs" 
"Elapsed time: 46.721892 msecs" 
4999950000 4999950000 4999950000) 

無反射的警告,沒有必要提示。通過將aget綁定到根變量或let中可以看到相同的行爲。

=> (let [f aget] (time (reduce + (for [x xa] (f xa x))))) 
"Elapsed time: 43.912129 msecs" 
4999950000 

任何想法爲什麼一個約束的aget似乎'知道'如何優化,其中的核心功能不?

回答

2

它與aget上的:inline指令有關,其擴展爲(. clojure.lang.RT (aget ~a (int ~i)),而正常的函數調用涉及Reflector。試試這些:

user> (time (reduce + (map #(clojure.lang.Reflector/prepRet 
     (.getComponentType (class xa)) (. java.lang.reflect.Array (get xa %))) xa))) 
"Elapsed time: 63.484 msecs" 
4999950000 
user> (time (reduce + (map #(. clojure.lang.RT (aget xa (int %))) xa))) 
Reflection warning, NO_SOURCE_FILE:1 - call to aget can't be resolved. 
"Elapsed time: 2390.977 msecs" 
4999950000 

你可能想知道什麼是內聯點。好了,看看這些結果:

user> (def xa (int-array (range 1000000))) ;; going to one million elements 
#'user/xa 
user> (let [f aget] (time (dotimes [n 1000000] (f xa n)))) 
"Elapsed time: 187.219 msecs" 
user> (time (dotimes [n 1000000] (aget ^ints xa n))) 
"Elapsed time: 8.562 msecs" 

事實證明,在你的榜樣,只要你過去反射警告,新的瓶頸是reduce +一部分,而不是數組訪問。這個例子消除了這種情況,並顯示了類型暗示的內聯aget的數量級優勢。

1

當您通過高階函數調用時,所有參數都將轉換爲對象。在這些情況下,編譯器無法找出被調用函數的類型,因爲在編譯函數時該函數未被綁定。只能確定它會是一些可以用一些論據來調用的東西。沒有警告打印,因爲任何東西都可以工作

user> (map aget (repeat xa) (range 100)) 
(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99) 

你已經發現clojure編譯器放棄的邊緣,只是使用對象的一切。 (這是一個過於簡單的解釋)

如果你把它包裝在任何自己編譯的東西上(比如匿名函數),那麼警告會再次顯示,儘管它們來自編譯匿名函數,而不是編譯調用映射。

user> (map #(aget %1 %2) (repeat xa) (range 100)) 
Reflection warning, NO_SOURCE_FILE:1 - call to aget can't be resolved. 
(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99) 

然後警告消失時類型提示添加到匿名,雖然不變,函數調用。

+0

我不明白的是,如果clojure編譯器爲所有東西使用對象,爲什麼性能幾乎與類型提示版本相同,而不是沒有給出類型提示的最差情況性能版本? – NielsK 2012-04-13 17:46:21

+0

JVM中的HotSpot編譯器對於對象來說很奇怪(對我來說)和奇妙的事物......最糟糕的情況是不得不反思在類中找到每個函數的名字,至少你是在避免那個:) – 2012-04-13 17:49:54

+0

我是隻討論調用作爲參數傳遞的函數。在被調用的函數在編譯時固定的情況下(「正常」情況),優化圖片更好, – 2012-04-13 17:56:52