TL;DR - Using
applyin performance sensitive code is not a good idea.
Building on the last blog post on
defrecord, I decided to investigate more about
defrecord can be thought of as “bags of data”,
defprotocol defines beaviour for those.
It’s maybe worth pointing out that if you define a protocol with multiple arities that CLJS will compile to a drastically more involved & slower code, causing 1 extra loop to copy arguments (never understood why) and 2 extra dispatch functions per each invocation (one for varargs and aritity selection, the other as you described). So if performance matters, define protocols in such a way that each protocol fn only uses a single arity…
hmmm…. I decided to investigate this claim.
Down the rabbit hole
(defprotocol P (foo [this a]) (bar-me [this a] [this a b])) (deftype Foo  P (foo [this a] 1) (bar-me [this a] 1) (bar-me [this a b] 1))
bar_me- which examines the number of arguments given to the function given and then decides to call the appropriate arity function.
bar-me2 arity js generated function (1 arity for the detype and another for parameter
bar-me3 arity js generated function.
(bar-me (Foo.) 1) generates
Whoaa! ClojureScript compiler already knows the number of appropriate arguments to the
bar-me function. So it emits a direct call to the function 2 from above without going through function 1. This is counter to the claim made by toxi.
The only circumstance where the claim may be valid is when the compiler does not know the number of arguments passed i.e. the number of arguments is dynamic. This happens if
bar-me is used via
How slow, lets find out below.
Before I benchmark the protocols, I decided to set the base case to be - by explictly attaching a function
This adds a method called
Foo data type and it has similar behaviour to the
foo function in the
I tested the performance of the following:
All benchmarks were run on Mac OS X in Safari Technical Preview [Version 9.1.1 (11601.6.17, 11602.1.33)] browser.
|Simple protocol -
|Simple protocol via apply||2.02819105288781E-07||221x|
As suspected calling multi-arity protocols is no different from calling simple protocols if done directly. But boy, is
Also the timing difference between calling
foo gives us cost of using protocols. Its the cost associated with checking if the datatype is not null and checking if the appropriate method exists on the datatype.
Till next time!