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.

Reply via email to