Performance of calling primitive type hinted functions passed as arguments

118 views
Skip to first unread message

Alice

unread,
Apr 24, 2013, 11:35:11 AM4/24/13
to Clojure
(defn foo [^long l cb] (cb l))

(time
(dotimes [n 1000000]
(foo n (fn [l] nil))))

(time
(dotimes [n 1000000]
(foo n (fn [^long l] nil))))

"Elapsed time: 7.861 msecs"
"Elapsed time: 11.770973 msecs"


Why is the latter slower?

Jim

unread,
Apr 24, 2013, 11:38:54 AM4/24/13
to clo...@googlegroups.com
You should be getting very similar timings from these 2 versions as the
type-hinted arg 'l' is never used anywhere - you're just returning nil...

Jim

Alice

unread,
Apr 24, 2013, 11:47:32 AM4/24/13
to Clojure
I tested several times, but the latter is always slower.

Stuart Sierra

unread,
Apr 24, 2013, 11:55:31 AM4/24/13
to clo...@googlegroups.com
I'm taking a guess here: The compiler doesn't know the type signature of `cb` when compiling `foo`, so it's going to use the IFn.invoke(Object) signature. Clojure's type inference is only local, and it won't assume that a primitive-type signature is available for an arbitrary function.

So there's probably some extra typecasting going on when `fn` is type-hinted to a primitive.

In general, type-hinting to primitive types doesn't do you any good in the presence of higher-order functions like `map`.

-S

Softaddicts

unread,
Apr 24, 2013, 11:55:56 AM4/24/13
to clo...@googlegroups.com
I am not convinced that this test is representative.
If you are not using l but still hinting it's type, you may have extra
code generated in the function body to handle the type hint.

4 milliseconds divided by a million is a very low overhead IMHO.
I would hardly qualify this as being inefficient.

I would expect to see a gain if you had some calls done on the argument to
qualify how much you save by avoiding reflection.

I did not open the cover of the byte code generated by the compiler however.
It could be an edge case not optimized yet. On the other hand if the argument is not
used at all, why bother with such an edge case ?

Luc P.
> --
> --
> 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/groups/opt_out.
>
>
>
--
Softaddicts<lprefo...@softaddicts.ca> sent by ibisMail from my ipad!

Alice

unread,
Apr 24, 2013, 12:29:54 PM4/24/13
to Clojure
I'm writing some low-level byte manipulation library and want it to be
as efficient as possible, so I just want to know the cost of trivial
things like this before designing the API. It can be negligible as you
said, but I wanted to know why it is slow and what's happening under
the hood anyway.
> Softaddicts<lprefonta...@softaddicts.ca> sent by ibisMail from my ipad!

Alice

unread,
Apr 24, 2013, 1:15:49 PM4/24/13
to Clojure
So, is there a way to type hint on cb that it has a function accepting
a long argument?

On Apr 25, 12:55 am, Stuart Sierra <the.stuart.sie...@gmail.com>
wrote:

Jason Wolfe

unread,
Apr 24, 2013, 8:59:00 PM4/24/13
to clo...@googlegroups.com
^clojure.lang.IFn$LO should work, although my guess is that this is considered an implementation detail and subject to change with new versions of Clojure.

Mikera

unread,
Apr 25, 2013, 12:00:50 AM4/25/13
to clo...@googlegroups.com
The second one is slower because the (cb l) call is going via (fn [Object]) which then delegates to (fn [long]) rather than using (fn [Object]) directly. I believe it may be doing an extra cast as well, which would explain the extra 3ns of overhead.

In general, I've found primitive functions are very tricky to get working at maximum performance within higher order functions: they are mostly useful for direct calls with known arguments. 

If you really care about performance then you might find that using Java interfaces or abstract base classes works better: there are easier to type hint, it can give you a bit more control and doesn't have as many restrictions / strange edge cases. Also they work nicely for protocol dispatch. I use this technique quite a lot within vectorz-clj to squeeze the maximum performance out or low-level vector/matrix operations. Downside it that it isn't idiomatic Clojure..... but currently you have to live with that if you want maximum performance.

Gunnar Völkel

unread,
Apr 25, 2013, 3:17:10 AM4/25/13
to clo...@googlegroups.com
Some time ago I dug into primitive type hints and how the Clojure compiler uses them.
When the compiler finds primitive type hints on a function, say (defn f [^long n] ...), the generated class for that function implements a primitive interface, IFn$LO in this case,
and generates appropriate code in the interface's only method Object invokePrim(long arg0).

For a defn which is used as symbol in code the compiler detects the primitive interface and generates code using invokePrim if the infered type of the parameters matches.
Long story short, currently the compiler is not able to use primitive invocation for higher order functions automatically because it would need to generate code that checks at runtime.

You can fix this with Java interop. I implemented an `invoke-primitive` macro over here: https://gist.github.com/guv/5458038
It could be used like follows:
(defn calc [^long n] ...)
(defn dosomething [f, ^long n]
  (invoke-primitive O [L] f n))

The macro expands to (.invokePrim ^IFn$LO f n) using several checks at compile time.

Alice

unread,
Apr 25, 2013, 5:50:28 AM4/25/13
to Clojure
Wow! That's awesome. It's even faster! Thanks.

(defn foo1 [^long l cb] (cb l))
(defn foo2 [^long l cb] (invoke-primitive O [L] cb l))

(time
(dotimes [n 1000000]
(foo1 n (fn [l] nil))))

(time
(dotimes [n 1000000]
(foo2 n (fn [^long l] nil))))

"Elapsed time: 7.622627 msecs"
"Elapsed time: 5.341534 msecs"

On Apr 25, 4:17 pm, Gunnar Völkel <gunnar.voel...@googlemail.com>
wrote:

Alice

unread,
Apr 25, 2013, 6:09:54 AM4/25/13
to Clojure
The reason foo2 is faster is that foo1 is passing a primitive long
value to cb, which caused boxing. Without that, the performance seems
to be exactly the same, which it should be!
Reply all
Reply to author
Forward
0 new messages