Strange aget optimisation behavior

久未见 提交于 2019-12-10 17:34:57

问题


Followup on this question about aget performance

There seems to be something very strange going on optimisation wise. We knew the following was true:

=> (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.

However, some further experimenting really weirded me out:

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

No reflection warnings, no hints needed. Same behavior is seen by binding aget to a root var or in a let.

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

Any idea why a bound aget seems to 'know' how to optimise, where the core function doesn't ?


回答1:


It has to do with the :inline directive on aget, which expands to (. clojure.lang.RT (aget ~a (int ~i)), whereas the normal function call involves the Reflector. Try these:

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

You might wonder what's the point of inlining, then. Well, check out these results:

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"

It turns out that in your example, as soon as you get past reflection warnings, your new bottleneck is the reduce + part and not array access. This example eliminates that and shows an order-of-magnitude advantage of the type-hinted, inlined aget.




回答2:


when you call through a higher order function all arguments are cast to object. In these cases the compiler can't figure out the type for the function being called because it is unbound when the function is compiled. It can only be determined that it will be something that can be called with some arguments. No warning is printed because anything will work.

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)

you have found the edge where the clojure compiler gives up and just uses object for everything. (this is an oversimplified explanation)

if you wrap this in anything that gets compiled on it own (like an anonymous function) then the warnings become visible again, though they come from compiling the anonymous function, not form compiling the call to 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)

and then the warning goes away when a type hint is added to the anonymous, though unchanging, function call.



来源:https://stackoverflow.com/questions/10144937/strange-aget-optimisation-behavior

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!