A discussion about this on Clojurians slack: https://clojurians.slack.com/archives/C03L9H1FBM4/p1728361006109229 A couple of highlights: - apply is very slow and allocating. Desctucturing is suboptimal, especially for lists. - If you look at the apply method's implementation <https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/AFn.java>, you see why it is much slower than regular fn call. It walks the seq to get each of the fn args, then calls invoke. It's a lot of overhead.
On Saturday, 5 October 2024 at 17:59:32 UTC+2 Jules wrote: > I was just checking to see how much overhead I might pay using apply > rather than destructuring a collection of args so I could call a function > directly on them, when I found something I thought interesting: > > TLDR: > > - apply is really slow - 2 orders of magnitude slower than a direct call > - apply on an array, which I thought would be a direct way to the java > varargs api is really slow > - destructuring a list is two orders of magnitude slower than a vector > - it is an order of magnitude faster to destructure a vector and may a > direct call, than to use apply > - etc > > openjdk full version "21.0.4+7" > clojure 1.12.0 > AMD Ryzen 9 5950X 16-Core Processor > > If I've made any silly mistakes, please point them out and I'll run these > benchmarks again. > > If I am correct then there should be a lot of room to speed up apply and > destructuring of lists ? > > Interested in peoples thoughts... > > > Jules > > > m3.repl> ;; let's have function that takes 4 args > m3.repl> (defn foo [a b c d]) > #'m3.repl/foo > m3.repl> ;; let's try some direct calls > m3.repl> (time (dotimes [_ 100000000] (foo 1 2 3 4))) > "Elapsed time: 65.149019 msecs" > nil > m3.repl> (time (dotimes [_ 100000000] (foo 1 2 3 4))) > "Elapsed time: 49.920188 msecs" > nil > m3.repl> (time (dotimes [_ 100000000] (foo 1 2 3 4))) > "Elapsed time: 48.02132 msecs" > nil > m3.repl> ;; now lets try with a variety of collections via apply > m3.repl> (def vargs [1 2 3 4]) > #'m3.repl/vargs > m3.repl> (def largs (list 1 2 3 4)) > #'m3.repl/largs > m3.repl> (def aargs (into-array Object [1 2 3 4])) > #'m3.repl/aargs > m3.repl> ;; a vector > m3.repl> (time (dotimes [_ 100000000] (apply foo vargs))) > "Elapsed time: 7166.218804 msecs" > nil > m3.repl> (time (dotimes [_ 100000000] (apply foo vargs))) > "Elapsed time: 7167.766347 msecs" > nil > m3.repl> (time (dotimes [_ 100000000] (apply foo vargs))) > "Elapsed time: 7154.58141 msecs" > nil > m3.repl> ;; a list > m3.repl> (time (dotimes [_ 100000000] (apply foo largs))) > "Elapsed time: 5657.564262 msecs" > nil > m3.repl> (time (dotimes [_ 100000000] (apply foo largs))) > "Elapsed time: 5597.083287 msecs" > nil > m3.repl> (time (dotimes [_ 100000000] (apply foo largs))) > "Elapsed time: 5638.361902 msecs" > nil > m3.repl> ;; and an array > m3.repl> (time (dotimes [_ 100000000] (apply foo aargs))) > "Elapsed time: 7954.757485 msecs" > nil > m3.repl> (time (dotimes [_ 100000000] (apply foo aargs))) > "Elapsed time: 8125.750746 msecs" > nil > m3.repl> (time (dotimes [_ 100000000] (apply foo aargs))) > "Elapsed time: 8151.5701 msecs" > nil > m3.repl> ;; is it faster to destructure first ? > m3.repl> ;; a vector > m3.repl> (time (dotimes [_ 100000000] (let [[a b c d] vargs] (foo a b c > d)))) > "Elapsed time: 904.68628 msecs" > nil > m3.repl> (time (dotimes [_ 100000000] (let [[a b c d] vargs] (foo a b c > d)))) > "Elapsed time: 902.818246 msecs" > nil > m3.repl> (time (dotimes [_ 100000000] (let [[a b c d] vargs] (foo a b c > d)))) > "Elapsed time: 905.498641 msecs" > nil > m3.repl> ;; a list > m3.repl> (time (dotimes [_ 100000000] (let [[a b c d] largs] (foo a b c > d)))) > "Elapsed time: 30043.489179 msecs" > nil > m3.repl> (time (dotimes [_ 100000000] (let [[a b c d] largs] (foo a b c > d)))) > "Elapsed time: 30159.705918 msecs" > nil > m3.repl> (time (dotimes [_ 100000000] (let [[a b c d] largs] (foo a b c > d)))) > "Elapsed time: 30289.946966 msecs" > nil > m3.repl> ;; an array > m3.repl> (time (dotimes [_ 100000000] (let [[a b c d] aargs] (foo a b c > d)))) > "Elapsed time: 14231.86232 msecs" > nil > m3.repl> (time (dotimes [_ 100000000] (let [[a b c d] aargs] (foo a b c > d)))) > "Elapsed time: 14081.057031 msecs" > nil > m3.repl> (time (dotimes [_ 100000000] (let [[a b c d] aargs] (foo a b c > d)))) > "Elapsed time: 14924.808985 msecs" > nil > m3.repl> ;; hmm... maybe destructuring the vector is taking a shortcut and > not building a new collection whereas the others are > m3.repl> (time (dotimes [_ 100000000] (let [[a b c d e] vargs] (foo a b c > d)))) > "Elapsed time: 1158.761312 msecs" > nil > m3.repl> (time (dotimes [_ 100000000] (let [[a b c d e] vargs] (foo a b c > d)))) > "Elapsed time: 570.542383 msecs" > nil > m3.repl> (time (dotimes [_ 100000000] (let [[a b c d e] vargs] (foo a b c > d)))) > "Elapsed time: 588.179669 msecs" > nil > m3.repl> (time (dotimes [_ 100000000] (let [[a b c d e] vargs] (foo a b c > d)))) > "Elapsed time: 589.489251 msecs" > nil > m3.repl> > -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. To view this discussion visit https://groups.google.com/d/msgid/clojure/61d30dda-0e70-4760-8853-182fd7a0dd15n%40googlegroups.com.