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
-~----------~----~----~----~------~----~------~--~---

Reply via email to