Permalink
Browse files

Re-precate ObjMap

The compiler will now emit code to construct ObjMaps for maps with
simple keys and up to cljs.compiler/obj-map-threshold entries. -assoc
onto ObjMap causes conversion to PersistentHashMap after
cljs.core.ObjMap/HASHTABLE_THRESHOLD updates (instances of ObjMap keep
their own counts initialized to 0 by cljs.core.ObjMap/fromObject and
incremented at each assoc / dissoc).

Map literals with complex keys and up to
cljs.compiler/array-map-threshold entries now produce
PersistentArrayMaps.

ObjMap now implements the IEditableCollection protocol, however calls
to transient on ObjMaps return TransientHashMap.

This commit also removes the deprecation notice previously attached to
ObjMap.
  • Loading branch information...
1 parent 6cc76b7 commit ca17c05d8ecafcba1b1f5bc5ada8148247b4d8e3 @michalmarczyk michalmarczyk committed with David Nolen Apr 21, 2012
Showing with 77 additions and 21 deletions.
  1. +26 −5 src/clj/cljs/compiler.clj
  2. +38 −16 src/cljs/cljs/core.cljs
  3. +13 −0 test/cljs/cljs/core_test.cljs
View
@@ -346,14 +346,35 @@
(emit-wrap env
(emits "cljs.core.with_meta(" expr "," meta ")")))
+(def ^:private array-map-threshold 16)
+(def ^:private obj-map-threshold 32)
+
(defmethod emit :map
[{:keys [env simple-keys? keys vals]}]
(emit-wrap env
- (emits "cljs.core.PersistentHashMap.fromArrays(["
- (comma-sep keys)
- "],["
- (comma-sep vals)
- "])")))
+ (cond
+ (and simple-keys? (<= (count keys) obj-map-threshold))
+ (emits "cljs.core.ObjMap.fromObject(["
+ (comma-sep keys) ; keys
+ "],{"
+ (comma-sep (map (fn [k v]
+ (with-out-str (emit k) (print ":") (emit v)))
+ keys vals)) ; js obj
+ "})")
+
+ (<= (count keys) array-map-threshold)
+ (emits "cljs.core.PersistentArrayMap.fromArrays(["
+ (comma-sep keys)
+ "],["
+ (comma-sep vals)
+ "])")
+
+ :else
+ (emits "cljs.core.PersistentHashMap.fromArrays(["
+ (comma-sep keys)
+ "],["
+ (comma-sep vals)
+ "])"))))
(defmethod emit :vector
[{:keys [items env]}]
View
@@ -2933,16 +2933,27 @@ reduces them without incurring seq initialization"
(> a b) 1
:else 0)))
+(defn- obj-map->hash-map [m k v]
+ (let [ks (.-keys m)
+ len (.-length ks)
+ so (.-strobj m)
+ out (with-meta cljs.core.PersistentHashMap/EMPTY (meta m))]
+ (loop [i 0
+ out (transient out)]
+ (if (< i len)
+ (let [k (aget ks i)]
+ (recur (inc i) (assoc! out k (aget so k))))
+ (persistent! (assoc! out k v))))))
+
;;; ObjMap
-;;; DEPRECATED
-;;; in favor of PersistentHashMap
-(deftype ObjMap [meta keys strobj ^:mutable __hash]
+
+(deftype ObjMap [meta keys strobj update-count ^:mutable __hash]
Object
(toString [this]
(pr-str this))
IWithMeta
- (-with-meta [coll meta] (ObjMap. meta keys strobj __hash))
+ (-with-meta [coll meta] (ObjMap. meta keys strobj update-count __hash))
IMeta
(-meta [coll] meta)
@@ -2981,16 +2992,21 @@ reduces them without incurring seq initialization"
IAssociative
(-assoc [coll k v]
(if (goog/isString k)
- (let [new-strobj (goog.object/clone strobj)
- overwrite? (.hasOwnProperty new-strobj k)]
- (aset new-strobj k v)
+ (let [overwrite? (.hasOwnProperty strobj k)]
(if overwrite?
- (ObjMap. meta keys new-strobj nil) ; overwrite
- (let [new-keys (aclone keys)] ; append
- (.push new-keys k)
- (ObjMap. meta new-keys new-strobj nil))))
+ (let [new-strobj (goog.object/clone strobj)]
+ (aset new-strobj k v)
+ (ObjMap. meta keys new-strobj (inc update-count) nil)) ; overwrite
+ (if (< update-count cljs.core.ObjMap/HASHMAP_THRESHOLD) #_(< (.-length keys) cljs.core.ObjMap/HASHMAP_THRESHOLD)
+ (let [new-strobj (goog.object/clone strobj) ; append
+ new-keys (aclone keys)]
+ (aset new-strobj k v)
+ (.push new-keys k)
+ (ObjMap. meta new-keys new-strobj (inc update-count) nil))
+ ;; too many keys, switching to PersistentHashMap
+ (obj-map->hash-map coll k v))))
; non-string key. game over.
- (with-meta (into (hash-map k v) (seq coll)) meta)))
+ (obj-map->hash-map coll k v)))
(-contains-key? [coll k]
(obj-map-contains-key? k strobj))
@@ -3001,18 +3017,24 @@ reduces them without incurring seq initialization"
new-strobj (goog.object/clone strobj)]
(.splice new-keys (scan-array 1 k new-keys) 1)
(js-delete new-strobj k)
- (ObjMap. meta new-keys new-strobj nil))
+ (ObjMap. meta new-keys new-strobj (inc update-count) nil))
coll)) ; key not found, return coll unchanged
IFn
(-invoke [coll k]
(-lookup coll k))
(-invoke [coll k not-found]
- (-lookup coll k not-found)))
+ (-lookup coll k not-found))
+
+ IEditableCollection
+ (-as-transient [coll]
+ (transient (into (hash-map) coll))))
+
+(set! cljs.core.ObjMap/EMPTY (ObjMap. nil (array) (js-obj) 0 0))
-(set! cljs.core.ObjMap/EMPTY (ObjMap. nil (array) (js-obj) 0))
+(set! cljs.core.ObjMap/HASHMAP_THRESHOLD 32)
-(set! cljs.core.ObjMap/fromObject (fn [ks obj] (ObjMap. nil ks obj nil)))
+(set! cljs.core.ObjMap/fromObject (fn [ks obj] (ObjMap. nil ks obj 0 nil)))
;;; HashMap
;;; DEPRECATED
@@ -1148,6 +1148,19 @@
(map #(vector % %)
(range (* 2 array-map-conversion-threshold)))))))
+ ;; literal maps
+ (loop [m1 {} m2 {} i 0]
+ (if (< i 100)
+ (recur (assoc m1 i i) (assoc m2 (str "foo" i) i) (inc i))
+ (do (assert (= m1 (into cljs.core.PersistentHashMap/EMPTY
+ (map vector (range 100) (range 100)))))
+ (assert (= m2 (into cljs.core.PersistentHashMap/EMPTY
+ (map vector
+ (map (partial str "foo") (range 100))
+ (range 100)))))
+ (assert (= (count m1) 100))
+ (assert (= (count m2) 100)))))
+
;; TransientHashSet
(loop [s (transient #{})
i 0]

0 comments on commit ca17c05

Please sign in to comment.