这是为了回应我给 StackOverflow 上的一个回答(我希望这是大部分正确的,但我不够经验丰富,不能确信)。有关此问题,那里有比在这个问题中更多的详细信息。
概括来说,(:k my-set) 比 (my-set :k) 和 (:k my-map) 慢得多。这非常反直观,因为我保留了一些我想重复查询成员资格的项目,我把它们保留在集合中。把它们保存在映射中始终是性能更高的选择(映射在两种调用的形式中都运行良好)。
我发现延迟差异的原因是,调用集合的 invoke 比调用键控的 invoke 快得多,后者进行了大量委托和 instanceof 检查。
通过使用 proxy 实现 ILookup,我能够提高 {:k my-set} 的性能。
(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
我想知道为什么 clojure 的集合最初不实现 ILookup?搜索不是人们希望从集合中期望做的很大一部分吗?他们已经有了完成这些功能所需的所有适当函数。如果他们只实现了 ILookup,那会破坏什么?或者还有其他不这么做的原因?
谢谢。
编辑
根据 @alexmiller 在评论中的建议,我还重新测试了 (contains? uids :o1),它的速度也比原始的 ILookup 实现慢。
(println "kw set")
(time (dotimes [i 1000000] (:o1 uids)))
(println "kw lookupable set")
(time (dotimes [i 1000000] (:o1 lookupable-set)))
(println "kw map")
(time (dotimes [i 1000000] (:o1 uids-map)))
(println "contains? set")
(time (dotimes [i 1000000] (contains? uids :o1)))
它给了我以下结果:
kw set
"消耗时间: 283.526096 毫秒"
kw lookupable set
"消耗时间: 121.766786 毫秒"
kw map
"消耗时间: 70.514017 毫秒"
contains? set
"消耗时间: 153.092212 毫秒"
与集合相比,映射仍然是搜索的 X2 倍快,为集合实现 ILookup 比使用 contains? 更快。