Sure enough, I get the same results user=> (defn as-str [& args] (apply str (map #(if (instance? clojure.lang.Named %) (name %) %) args))) #'user/as-str user=> (time (dotimes [i 1000000] (as-str :a))) "Elapsed time: 416.497348 msecs" nil user=> (time (dotimes [i 1000000] (as-str :a 1))) "Elapsed time: 1023.526175 msecs"
Apparently (defmacro as-str3 [& args] (cons 'str (map #(if (instance? clojure.lang.Named %) (name %) %) args))) runs as fast (or minusculy faster) than the original version that you posted: user=> (defn as-str2 "With no args, returns the empty string. With one arg, returns its name or string representation. (as-str nil) returns the empty string. With more than one arg, returns the concatenation of the as-str values of the args." {:tag String} ([] "") ([x] (if (instance? clojure.lang.Named x) (name x) (str x))) ([x & ys] (let [sb (StringBuilder. #^String (as-str x))] (doseq [y ys] (.append sb (as-str y))) (.toString sb)))) user=> (time (dotimes [i 1000000] (as-str2 :a))) "Elapsed time: 29.844296 msecs" user=> (time (dotimes [i 1000000] (as-str2 :a 1))) "Elapsed time: 1046.465682 msecs" user=> (defmacro as-str3 [& args] (cons 'str (map #(if (instance? clojure.lang.Named %) (name %) %) args))) nil user=> (time (dotimes [i 1000000] (as-str3 :a))) "Elapsed time: 28.533794 msecs" nil user=> (time (dotimes [i 1000000] (as-str3 :a 1))) "Elapsed time: 260.311017 msecs" Which sees to indicate that apply is actually what is slow... Sure enough, if we do user=> (defmacro apply2 [fn args] (cons fn 'args)) nil user=> (time (dotimes [i 1000000] (apply2 str (map #(if (instance? clojure.lang.Named %) (name %) %) '(:a))))) "Elapsed time: 28.215038 msecs" user=> (time (dotimes [i 1000000] (apply2 str (map #(if (instance? clojure.lang.Named %) (name %) %) '(:a 1))))) "Elapsed time: 262.348148 msecs" I tried a couple times to use apply2 in a defn or defmacro for as-str, but kept on getting weird exceptions. I'm probably just tired and doing something wrong, so I'll just leave it at that for now... Perhaps apply needs to be revisited? --Eric Tschetter On Thu, Apr 9, 2009 at 6:49 AM, Stephen C. Gilardi <squee...@mac.com> wrote: > > On Apr 9, 2009, at 2:57 AM, Eric Tschetter wrote: > >> Might I suggest >> >> (defn as-str >> ([] "") >> ([& args] >> (apply str (map #(if (instance? clojure.lang.Named %) (name %) %) >> args))) > > I like it for its simplicity. It can actually be a little simpler yet as the > general case gives the same result when passed no arguments as the special > case does: > > (defn as-str > [& args] > (apply str (map #(if (instance? clojure.lang.Named %) (name %) %) args))) > > This code works well, but performs much less well than the code I posted. > Just by code inspection, we can see that it's doing a lot more work, > invoking a lot more machinery, especially in the one-argument case. > > We can run some micro-benchmarks to get some idea how that affects its > performance: > > For a one argument case: > > ; as-str using apply > user=> (time (dotimes [i 1000000] (as-str :a))) > "Elapsed time: 496.361 msecs" > > ; as-str not using apply > user=> (time (dotimes [i 1000000] (as-str :a))) > "Elapsed time: 22.178 msecs" > > The non-apply version is 22 times as fast. > > For a two argument case: > > ; as-str using apply > user=> (time (dotimes [i 1000000] (as-str :a 1))) > "Elapsed time: 1057.922 msecs" > > ; as-str not using apply > user=> (time (dotimes [i 1000000] (as-str :a 1))) > "Elapsed time: 317.483 msecs" > > The non-apply version is 3.3 times as fast. > > From looking at this difference, my conclusion is that the version I posted > is "simple enough" to be preferred over the apply version given that its > performance, especially in the one argument case, is so much better. > > Criticisms of that conclusion: > > - These are micro-benchmarks, they tell us little about how either > version will really perform when the processor is doing more than just > repeating the same operation over and over. > > - This is premature optimization, there's no evidence that this > difference will matter in practice. > > - The apply version is simple, functional, and pretty. In the long > run that matters more than performance. > > Thoughts on the criticisms: > > micro-benchmark: that's right, although, combined with thinking about > how the code works, it does give some indication of the performance > difference which is not 10%, or 60%, but 2100%. This is library code that > should run well in a large variety of circumstances. A factor of 22 in > performance is hard to ignore in that scenario. > > premature: that's right. It's only the "this is a low-level operation > that may be used heavily in circumstances we don't yet imagine" argument > that pushes me to consider this optimization in the absence of a real > performance test that shows it matters. > > simple: The apply version is certainly pretty. The version I posted, > though, is also written in a straightforward way that I think is quite > readable and understandable. It trades that last measure of simplicity for a > healthy measure of improved performance. > > Since arguing with myself is only so productive, I'd appreciate hearing > other thoughts about these ideas. > > --Steve > > --~--~---------~--~----~------------~-------~--~----~ 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 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 -~----------~----~----~----~------~----~------~--~---