On Apr 10, 2009, at 3:55 AM, Eric Tschetter wrote:

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"

Right, I get numbers in the same ballpark on my machine.

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"

The occurrences of "as-str" near the bottom of the the definition of as-str2 should be as-str2 instead.

Also it seems to take a few repetitions of the "dotimes" call for the time to settle down and become fairly consistent. In all my timings, I've given what appears to be a representative run, not the first run.

With the corrected as-str2, I'm getting numbers like these on my machine:

user=>  (time (dotimes [i 1000000] (as-str2 :a)))
"Elapsed time: 28.319 msecs"
user=>  (time (dotimes [i 1000000] (as-str2 :a 1)))
"Elapsed time: 325.617 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"

That turns out not to be the same test. The macro as-str3 gets fully expanded at macro expansion time. At runtime, what you're iterating is the expansion:

user=> (as-str3 :a)
"a"
user=> (macroexpand '(as-str3 :a))
(str "a")

For literal arguments to as-str3 (as we've used in the micro- benchmark), doing the operation at macro expansion time is a win. In the general case, though, we need the as-str facility to evaluate its arguments:

user=> (def i :a)
#'user/i
user=> (as-str2 i)
"a"
user=> (as-str3 i)
"i"

Mapping across the arguments needs to be done at runtime (which in this case means it will be done a million times), not macro expansion time (which in this case means it will be done once).

Which seems to indicate that apply is actually what is slow...

Using apply appears to be a somewhat slower than a direct call:

user=> (time (dotimes [i 1000000] (str "a" "b" "c" "d")))
"Elapsed time: 437.493 msecs"

user=> (time (dotimes [i 1000000] (apply str ["a" "b" "c" "d"])))
"Elapsed time: 731.039 msecs"

user=> (time (dotimes [i 1000000] (apply str "a" "b" ["c" "d"])))
"Elapsed time: 1061.53 msecs"

user=> (time (dotimes [i 1000000] (apply str "a" "b" "c" "d" [])))
"Elapsed time: 1301.953 msecs"

However, the big difference you saw was the macro difference described above.

--Steve

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

Reply via email to