You can possibly get rid of the var dereference by dereferencing at compile-time. Of course this means you lose the benefits of vars, too. It's just super-easy when needed :-).
(defn a [] 1) (defn b [] (dotimes [_ 1000000000] (a))) (time (b)) "Elapsed time: 1097.082556 msecs" (time (b)) "Elapsed time: 1087.100662 msecs" nil (let [a a] (defn b [] (dotimes [_ 1000000000] (a)))) (time (b)) "Elapsed time: 734.876062 msecs" (time (b)) "Elapsed time: 731.460797 msecs" On Sun, Apr 6, 2014 at 3:50 PM, Andy Fingerhut <andy.finger...@gmail.com>wrote: > I would not say "shame on you" -- it is reasonably common to find Clojure > code from experienced Clojure developers that puts type hints in places > where they do not have the desired effect. It isn't an error, so there are > no error messages. Finding occurrences of such things while I have been > testing the Eastwood Clojure lint tools is one of the reasons I created the > following enhancement issue for it, so in the future it may be able to give > warnings about such things: > > https://github.com/jonase/eastwood/issues/37 > > Andy > > > On Sun, Apr 6, 2014 at 12:40 PM, Dmitry Groshev <lambdadmi...@gmail.com>wrote: > >> Shame on me! Now the difference is around 10%. Much better! >> >> Case closed, I suppose :) >> >> On Sunday, April 6, 2014 11:24:05 PM UTC+4, Andy Fingerhut wrote: >> >>> Sorry I am not taking the time to try out the change for you and see >>> whether it makes the desired difference in performance happen, but I do >>> know that the ^double type hint on return values should be on the argument >>> vector, not on the var. So instead of your abs-diff, try this, and >>> similarly for any other functions with double return value: >>> >>> (defn abs-diff ^double [^double x ^double y] >>> (if (p/> x y) (p/- x y) (p/- y x))) >>> >>> Andy >>> >>> >>> On Sun, Apr 6, 2014 at 9:44 AM, Dmitry Groshev <lambda...@gmail.com>wrote: >>> >>>> For some time I had a suspicion that in Clojure we have a fundamental >>>> problem with efficient math and today I've tried to test it. >>>> >>>> Let's say we have a pretty simple function: >>>> >>>> (ns testapp.core >>>> (:require >>>> [criterium.core :as cr] >>>> [primitive-math :as p] >>>> [no.disassemble :as nd])) >>>> >>>> (set! *warn-on-reflection* true) >>>> >>>> (defn ^double test1 [a b] >>>> (let [^doubles a a >>>> ^doubles b b] >>>> (areduce a i ret (double 0) >>>> (p/+ ret >>>> (let [a-x (aget a i) >>>> b-x (aget b i)] >>>> (if (p/> a-x b-x) >>>> (p/- a-x b-x) >>>> (p/- b-x a-x))))))) >>>> >>>> Let's test it's performance (all tests were performed in Clojure 1.5.1 >>>> on top of latest JVM7 and Sandy Bridge i5): >>>> >>>> (defn bench1 [] >>>> (let [a (double-array (range 1 100000)) >>>> b (double-array (range 100000 1 -1))] >>>> (cr/quick-bench (test1 a b)))) >>>> >>>> Result: >>>> >>>> Execution time mean : 223.076094 µs >>>> Execution time std-deviation : 21.413850 µs >>>> >>>> It's around 1 nanosecond per double operation, so it looks adequate to >>>> me. Disassemble looks fine to me, too. >>>> >>>> Now let's refactor the code a little, that compare-and-diff piece looks >>>> generally useful: >>>> >>>> (defn ^double abs-diff [^double x ^double y] >>>> (if (p/> x y) (p/- x y) (p/- y x))) >>>> >>>> (defn ^double test2 [a b] >>>> (let [^doubles a a >>>> ^doubles b b] >>>> (areduce a i ret (double 0) >>>> (p/+ ret (double (abs-diff (aget a i) (aget b i))))))) >>>> >>>> (defn bench2 [] >>>> (let [a (double-array (range 1 100000)) >>>> b (double-array (range 100000 1 -1))] >>>> (cr/quick-bench (test2 a b)))) >>>> >>>> Suddenly: >>>> >>>> Execution time mean : 877.690451 µs >>>> Execution time std-deviation : 15.281713 µs >>>> >>>> Why is it so? Disassemble reveals a few inefficiencies here and there. >>>> Here is an interesting bit of test2's invoke: >>>> >>>> 32 lload 6 [i] >>>> 34 lconst_1 >>>> 35 ladd >>>> 36 dload 8 [ret] >>>> 38 getstatic testapp.core$test2.const__5 : clojure.lang.Var [57] >>>> 41 invokevirtual clojure.lang.Var.getRawRoot() : java.lang.Object >>>> [76] >>>> 44 checkcast clojure.lang.IFn$DDO [78] >>>> 47 aload_3 [a] >>>> 48 checkcast double[] [72] >>>> 51 lload 6 [i] >>>> 53 invokestatic clojure.lang.RT.intCast(long) : int [82] >>>> 56 daload >>>> 57 aload 4 [b] >>>> 59 checkcast double[] [72] >>>> 62 lload 6 [i] >>>> 64 invokestatic clojure.lang.RT.intCast(long) : int [82] >>>> 67 daload >>>> 68 invokeinterface clojure.lang.IFn$DDO.invokePrim(double, >>>> double) : java.lang.Object [86] [nargs: 5] >>>> 73 invokestatic clojure.lang.RT.doubleCast(java.lang.Object) : >>>> double [90] >>>> 76 invokestatic primitive_math.Primitives.add(double, double) : >>>> double [96] >>>> 79 dstore 8 [ret] >>>> 81 lstore 6 [i] >>>> 83 goto 19 >>>> 86 goto 95 >>>> >>>> If I understand this correctly, in this piece of code we have following >>>> problems: >>>> 1) there are some unnecessary casts from long to int. Probably this is >>>> caused by areduce macro (it uses "0" as a counter and it's long in Clojure, >>>> but arrays are indexed with ints). Replacing it with a loop doesn't help >>>> much, maybe JIT optimizes this already. >>>> 2) every call to abs-diff goes through Var dereference. >>>> 3) abs-diff returns Object despite the ^double return annotation (this >>>> is why (double ...) cast is needed there to avoid reflection warning). >>>> Therefore we can't avoid boxing-unboxing here. >>>> >>>> Is there a way to use small, reusable, composable pieces in math-heavy >>>> hot loops? We can use macros instead of functions and compile everything >>>> into one big method. This is the way HipHip(array) of Prismatic works. The >>>> problem here is that their examples are tiny and bigger stuff like >>>> decompositions will result in very large compiled methods. This is a bad >>>> practice in Java-land, because in general JVM doesn't like such methods, >>>> skipping their inlining and delaying optimization. Moreover, macros have >>>> composability problems, too. Therefore, macros are not the ultimate >>>> solution here. >>>> >>>> So, what's the best way to write beautiful AND performant numerical >>>> code in Clojure? >>>> >>>> The full code for this email can be found here: >>>> https://gist.github.com/si14/10008500 >>>> >>>> -- >>>> You received this message because you are subscribed to the Google >>>> Groups "Clojure" group. >>>> To post to this group, send email to clo...@googlegroups.com >>>> >>>> Note that posts from new members are moderated - please be patient with >>>> your first post. >>>> To unsubscribe from this group, send email to >>>> clojure+u...@googlegroups.com >>>> >>>> For more options, visit this group at >>>> http://groups.google.com/group/clojure?hl=en >>>> --- >>>> You received this message because you are subscribed to the Google >>>> Groups "Clojure" group. >>>> To unsubscribe from this group and stop receiving emails from it, send >>>> an email to clojure+u...@googlegroups.com. >>>> >>>> For more options, visit https://groups.google.com/d/optout. >>>> >>> >>> -- >> You received this message because you are subscribed to the Google >> Groups "Clojure" group. >> To post to this group, send email to clojure@googlegroups.com >> Note that posts from new members are moderated - please be patient with >> your first post. >> To unsubscribe from this group, send email to >> clojure+unsubscr...@googlegroups.com >> For more options, visit this group at >> http://groups.google.com/group/clojure?hl=en >> --- >> You received this message because you are subscribed to the Google Groups >> "Clojure" group. >> To unsubscribe from this group and stop receiving emails from it, send an >> email to clojure+unsubscr...@googlegroups.com. >> For more options, visit https://groups.google.com/d/optout. >> > > -- > You received this message because you are subscribed to the Google > Groups "Clojure" group. > To post to this group, send email to clojure@googlegroups.com > Note that posts from new members are moderated - please be patient with > your first post. > To unsubscribe from this group, send email to > clojure+unsubscr...@googlegroups.com > For more options, visit this group at > http://groups.google.com/group/clojure?hl=en > --- > You received this message because you are subscribed to the Google Groups > "Clojure" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to clojure+unsubscr...@googlegroups.com. > For more options, visit https://groups.google.com/d/optout. > -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.