我从未进入 clojure 的源代码,但从我看到的 set 实现 actually uses a map inside:
protected APersistentSet(IPersistentMap impl){
this.impl = impl;
}
它还将invoke 调用委托给内部映射。
在APersistentSet:
public Object invoke(Object arg1) {
return get(arg1);
}
// ....
public Object get(Object key){
return impl.valAt(key);
}
在APersistentMap:
public Object invoke(Object arg1) {
return valAt(arg1);
}
public Object invoke(Object arg1, Object notFound) {
return valAt(arg1, notFound);
}
所以这不能解释差异。
作为@cgrand 的mentioned in the comments,当我们反转参数时它更快(并且大致相同,因为我们立即调用set 的invoke)。所以我查了Keyword的invoke,这可能是(:k obj)使用的:
final public Object invoke(Object obj, Object notFound) {
if(obj instanceof ILookup)
return ((ILookup)obj).valAt(this,notFound);
return RT.get(obj, this, notFound);
}
需要注意的重要一点是ILookup 是在APersistentMap(通过Associative)中实现的,而不是在APersistentSet 中实现的。您也可以在 clojure 中进行验证:
(instance? clojure.lang.ILookup #{}) ;; false
(instance? clojure.lang.ILookup {}) ;; true
所以地图通过“快乐路径”并最终设置在RT.get,我认为这是运行时。
让我们看看运行时。
它最初尝试做与关键字几乎相同的事情:
static public Object get(Object coll, Object key){
if(coll instanceof ILookup)
return ((ILookup) coll).valAt(key);
return getFrom(coll, key);
}
但由于我们知道集合不实现ILookup,我们知道它们会转到RT.getFrom:
static Object getFrom(Object coll, Object key){
if(coll == null)
return null;
else if(coll instanceof Map) {
Map m = (Map) coll;
return m.get(key);
}
else if(coll instanceof IPersistentSet) {
IPersistentSet set = (IPersistentSet) coll;
return set.get(key);
}
else if(key instanceof Number && (coll instanceof String || coll.getClass().isArray())) {
int n = ((Number) key).intValue();
if(n >= 0 && n < count(coll))
return nth(coll, n);
return null;
}
else if(coll instanceof ITransientSet) {
ITransientSet set = (ITransientSet) coll;
return set.get(key);
}
return null;
}
这让我相信主要区别在于额外的委托和 instanceof 调用,因为集合没有实现 ILookup。
作为奖励,我在集合上添加了一个测试,该集合实现了ILookup,并立即将valAt 委托给内部映射(使用proxy),这稍微缩小了差距:
(def uids #{:a :b :c :d :e :f :g :h :i :j :k :l :m :n :o :p :a1 :b1 :c1 :d1 :e1 :f1 :h1 :i1 :j1 :k1 :l1 :m1 :n1 :o1 :p1})
(def uids-map (into {} (for [k uids] [k k])))
(def lookupable-set (proxy [clojure.lang.APersistentSet clojure.lang.ILookup] [uids-map]
(valAt [k] (get uids-map k))))
;; verify
(instance? clojure.lang.APersistentSet lookupable-set) ;; true
(instance? clojure.lang.ILookup lookupable-set) ;; true
(time (dotimes [i 1000000] (:o1 uids))) ;; 134.703101 msecs
(time (dotimes [i 1000000] (:o1 lookupable-set))) ;; 63.187353 msecs <-- faster
(time (dotimes [i 1000000] (:o1 uids-map))) ;; 35.802762 msecs <-- still fastest
总结:性能很重要 - 调用集合 (#{...} k) 而不通过关键字 (k #{...}) 与 map 一样快。
但我可能是错的:)