Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ClojureScript map lookup slow

I have a simple map:

(def my-map
  {[1 2 3] 1
   [1 2 4] 5
   [3 4 2] 3
   [4 5 3] 3
   [5 2 5] 6
   [9 2 1] 5
   [8 3 1] 6})

that I use for performing lookups. This performs rather poorly, however:

(time (doseq [x (range 500)]
        (my-map [1 2 8])))

"Elapsed time: 170 msecs"

On the same machine, Clojure can do 500,000 in about 236 msecs, or about 700x faster. While it's not unexpected for Clojure to be faster than ClojureScript, I'm confused why ClojureScript would be so much slower.

Any ideas about how I could make a simple multi-valued lookup map as above efficiently and in a readable manner in ClojureScript? I know doing a bunch of ifs instead of using a vector-key solution would certainly work faster, but I'm looking at something that's somewhat more readable / maintainable.

Just to update with more information. The above was done in Firefox, so thus slower than compared to V8. The following:

(def my-map2
  (into cljs.core.PersistentHashMap/EMPTY
        {[1 2 3] 1
         [1 2 4] 5
         [3 4 2] 3
         [4 5 3] 3
         [5 2 5] 6
         [9 2 1] 5
         [8 3 1] 6}))

(defn p1 []
  (let [v [1 2 8]]
    (dotimes [_ 5]
      (time (dotimes [_ 500000]
              (get my-map2 v))))))

gives:

"Elapsed time: 3295 msecs"

"Elapsed time: 3246 msecs"

"Elapsed time: 3113 msecs"

"Elapsed time: 3107 msecs"

"Elapsed time: 3121 msecs"

in Chromium Version 25.0.1364.160 Ubuntu 13.04 (25.0.1364.160-0ubuntu3). So still about 13x times slower in ClojureScript than Clojure, but that's much better than what it was before. Note also that I'm running this directly in browser repl.

like image 975
icyrock.com Avatar asked May 20 '13 01:05

icyrock.com


1 Answers

On my machine running your exact example with advanced compilation takes ~14ms on my 1.7ghz Macbook Air running a relatively recent v8 built from source.

To make sure we're benchmarking what we think we're benchmarking it's best to write something like this:

(let [v [1 2 8]]
  (dotimes [_ 5]
    (time
      (dotimes [_ 500000]
        (get my-map v)))))

On my machine this takes ~70ms on machine for Clojure JVM. ClojureScript runs this around ~3600ms, so about 50X slower. Why? It's because we default to PersistentArrayMap where Clojure does not when defining small hash maps with complex keys.

What happens if we define my-map like this instead:

(def my-map
  (into cljs.core.PersistentHashMap/Empty
    [[1 2 3] 1
     [1 2 4] 5
     [3 4 2] 3
     [4 5 3] 3
     [5 2 5] 6
     [9 2 1] 5
     [8 3 1] 6]))

The benchmark then takes ~170ms, which isn't so far off from Clojure JVM.

So there's definitely plenty of optimizations that Clojure implements that we haven't gotten around to yet. Still I'd say that for idiomatic Clojure code I think the best we can hope for on highly tuned JavaScript engines like V8 is 2-10X of Clojure JVM..

like image 55
dnolen Avatar answered Nov 15 '22 07:11

dnolen