Can anyone explain why the time jumps by an order of magnitude simply by wrapping this in a function?
user> (time (loop [n 0 t 0]
(if (= n 10000000)
t
(recur (inc n) (+ t n)))))
"Elapsed time: 29.312145 msecs"
49999995000000
user> (defn tl [r] (loop [n 0 t 0]
(if (= n r)
t
(recur (inc n) (+ t n)))))
#<Var@54dd6004: #object[user$eval3462$tl__3463 0x7d8ba46 "user$eval3462$tl__3463@7d8ba46"]>
user> (time (tl 10000000))
"Elapsed time: 507.333844 msecs"
49999995000000
I'm curious how a simply iteration like this can be made much faster. For example, a similar iterative loop in C++ takes less than 1 ms in Release mode, or about 20 ms in Debug mode on the same system as this Clojure code.
In summary: code is slowed down by the compilation and interpretation that occurs during runtime. Compare this to a statically typed, compiled language which runs just the CPU instructions once compilated. It's actually possible to extend Python with compiled modules that are written in C.
The reason these built-in functions are fast is that python's built-in functions, such as min, max, all, map, etc., are implemented in the C language. You should use these built-in functions instead of writing manual functions that will help you execute your code faster.
Functions CAN make code faster by coding logic once instead of repeating several times and thus reducing code size and resulting in better CPU cache usage. Functions CAN make code slower by copying parameters and hiding info from the optimization. Certain functions can be inlined to undo these disadvantages.
The purpose of wrapping is to a namespace and control the visibility of member functions. It wraps the code inside a function scope and decreases clashing with other libraries. This is what we call Immediately Invoked Function Expression (IIFE) or Self Executing Anonymous Function.
This happens because in second case passed argument is being boxed. Add type hint to fix this:
user> (defn tl [^long r]
(loop [n 0 t 0]
(if (= n r)
t
(recur (inc n) (+ t n)))))
user> (time (tl 10000000))
"Elapsed time: 20.268396 msecs"
49999995000000
UPD:
1, 2) In the first case you operate with java primitives, that's why it's so fast. ^Integer
won't work here, because it's type hint for boxed type java.lang.Integer
(that's why it's capitalized). ^long
is type hint exactly for java long
primitive. For function parameters you may do only ^long
and ^double
primitive type hints (others are not supported besides of these you may do type hints for all kinds of primitive arrays, like ^floats
, ^bytes
etc.).
3) Since passed argument is boxed this forces generic aritmethics for all operations. In other words, every +
and inc
operation will create new object on heap (in case of primitives, they will remain on stack).
UPD 2:
As an alternative to type hinting you may explicitly convert passed argument to primitive before the loop:
user> (defn tl [r]
(let [r (long r)]
(loop [n 0 t 0]
(if (= n r)
t
(recur (inc n) (+ t n))))))
user> (time (tl 10000000))
"Elapsed time: 18.907161 msecs"
49999995000000
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With