This ticket is about two (independent, but similar) ideas about making apply faster:
- Avoid walking the seq multiple times
- Avoid creating a seq in 3+ arity case of apply
Both of these issues MULTIPLY runtime, so if we avoid creating that sequence AND walk the sequence only once carefully we get a 10x..15x speedup.
Teaser: Current impl of applyTo will possibly walk the seq up to 3 times before actually calling the impl:
([x y] x)
([a b c, d e f] f)
([a b c, d e f & xs] f))
(apply r-6 1 2 3 [4 5 6])
First the seq is constructed, walked once in RestFn.applyTo, dispatched to AFn.applyToHelper, walked again to get the arity. Walk a last time to get the arguments out and call the implementation.
This code runs in about 310ns, with the patch it runs 10x faster in ~30ns. This is admittedly constructed example, though not unrealistic usage of apply. All other performance improvements are anywhere from 1x ... 15x. No performance regression seems to occur.
This (and many other ways) can be optimized by only ever walking the seq once: Simply check for nil or getRequiredArity as the seq is walked with first/next.
As for (2) I needed to add a new protocol as to not break backward compatibility with people only reifying IFn. The in apply for 3+ args we do an instanceof check and dispatch to the interface directly instead of constructing an intermediate seq.
PS: Similar CLJS ticket: https://dev.clojure.org/jira/browse/CLJS-2099