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.

Reply via email to