Welcome! Please see the About page for a little more info on how this works.

0 votes
in Clojure by

This ticket is about two (independent, but similar) ideas about making apply faster:

  1. Avoid walking the seq multiple times
  2. 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:

(defn r-6
([] 1)
([x] x)
([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

2 Answers

0 votes

Comment made by: aralo

Code for the patch was generated: https://github.com/rauhs/clj-bench/blob/master/src/clj_bench/apply.clj

So I think a review only needs to check the edge cases and not go over every single line of code. Benchmarking was tough. I could use some guidance. Though, i have seen no performance regressions in any case.

What I found: Benchmarks should call {{apply}} with both, RestFn AND AFn objects, since only calling it with one type will make the JVM optimize this case and provide some unrealistic number.

0 votes
Reference: https://clojure.atlassian.net/browse/CLJ-2319 (reported by aralo)