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

Attachment: smime.p7s
Description: S/MIME cryptographic signature

Reply via email to