4 ms·
In short: it's slow because there isn't just one kind of Java array, and if you don't say which one you mean, Clojure has to do reflection to figure it out. In
by aphyr 6y ago
In short: it's slow because there isn't just one kind of Java array, and if you don't say which one you mean, Clojure has to do reflection to figure it out.
Inside the JVM, there are several different types of arrays: bytes, floats, objects, etc. Clojure.lang.RT includes methods for getting the length of all of these types--for instance, here's the method for the length of arrays of longs:
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/RT.java#L2303 https://github.com/clojure/clojure/blob/master/src/jvm/cloju...
The clojure.core/alength function calls clojure.lang.RT/alength, but that callsite is polymorphic: the Clojure compiler doesn't know which of the RT functions to emit a call for, because it needs the type signature. If the type is unavailable at compile time, the Clojure compiler emits reflective code which inspects the type of the reference, determines which specific clojure.lang.RT/alength implementation to dispatch to, then executes that call dynamically. That's the slow part!
https://github.com/clojure/clojure/blob/clojure-1.10.1/src/clj/clojure/core.clj#L3903 https://github.com/clojure/clojure/blob/clojure-1.10.1/src/c...
Just like the article says, if you include a type hint, the compiler can emit an invokestatic call directly to, say, clojure.lang.RT.alength(long[]), and skip all the reflection.
- patrec 6y ago> In short: it's slow because there isn't just one kind of Java array, and if you don't say which one you mean, Clojure has to do reflection to figure it out. But it can't need any less reflection than "count" needs, and "count" is 100x faster. Surely whatever alength is doing in the non-type hinted case can't be... ideal? > If the type is unavailable at compile time, the Clojure compiler emits reflective code which inspects the type of the reference, determines which specific clojure.lang.RT/alength implementation to dispatch to, then executes that call dynamically. That's the slow part! But where do all those clock cycles go? Doing runtime type inspection and then dispatching to one of 9 static functions based on that doesn't feel like it ought to be anywhere near that terrible (especially in a JIT'ed language).
- patrec 6y agoJust to illustrate, some pretend-analogous run time type-inspect based dispatch code in python (a language not known for its blazing speed) is still much, much faster than this: In [3]: timeit a = [1]; a.__len__() if type(a) is list else len(a) 126 ns ± 0.615 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)