4

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.

しかし、さらに実験を重ねると、私は本当に奇妙になりました。

=> (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が最適化する方法を「知っている」ように見える理由はありますか?コア関数はそうではありませんか?

4

2 に答える 2

2

通常の関数:inline呼び出しagetには. これらを試してください:(. 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 +部分であり、配列へのアクセスではないことがわかります。この例はそれを排除し、タイプヒント付きの inlined の桁違いの利点を示していagetます。

于 2012-04-13T20:44:37.620 に答える
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 コンパイラーがあきらめて、すべてにオブジェクトを使用するだけのエッジを見つけました。(これは非常に単純化された説明です)

これを独自にコンパイルされるもの (無名関数など) でラップすると、警告は再び表示されますが、無名関数のコンパイルから発生したものであり、map への呼び出しのコンパイルから発生したものではありません。

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)

そして、型のヒントが無名の、変更されていない関数呼び出しに追加されると、警告は消えます。

于 2012-04-13T17:05:48.770 に答える