这是为了回答我在 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)))
结果如下:
关键字集合
"经过时间:283.526096 毫秒"
关键字可查找集合
"经过时间:121.766786 毫秒"
关键字映射
"经过时间:70.514017 毫秒"
包含?集合
"经过时间:153.092212 毫秒"
映射的速度仍然比集合快两倍用于查找,并且实现集合的 ILookup 比 contains?
更快。