似乎有一些非常奇怪的优化明智。我们知道以下情况属实:
=> (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似乎“知道”如何优化,核心功能不在哪里?
答案 0 :(得分: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 +
部分,而不是数组访问权限。这个例子消除了这一点,并显示了类型暗示的内联aget
的数量级优势。
答案 1 :(得分: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)
然后当类型提示添加到匿名但不变的函数调用时,警告消失。