2012-04-13 7 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をルートvarまたは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

+0

JVMのHotSpotコンパイラは、(私にとっては)奇妙なこととオブジェクトを使ったすばらしいことを行います...最悪の場合は、クラス内のすべての関数を名前で見つけて、少なくとも回避することです。 –

+0

私はパラメータとして渡された関数を呼び出すことについてのみ話しています。最適化画像は、呼び出されている関数がコンパイル時に固定されている場合(「通常の」場合) –