2024 年 Clojure 状态调查! 中分享您的想法。

欢迎!请查看 关于 页面以了解有关此信息。

+2
Collections
无法找到是否之前提出过,但似乎 {{empty?}}谓词对于临时集合是错误的。


user=> (empty? (transient []))
IllegalArgumentException 不知如何从 clojure.lang.PersistentVector$TransientVector 创建 ISeq:clojure.lang.RT.seqFrom (RT.java:528)

user=> (empty? (transient {}))
IllegalArgumentException 不知如何从 clojure.lang.PersistentArrayMap$TransientArrayMap 创建 ISeq:clojure.lang.RT.seqFrom (RT.java:528)

user=> (empty? (transient #{}))
IllegalArgumentException 不知如何从 clojure.lang.PersistentHashSet$TransientHashSet 创建 ISeq:clojure.lang.RT.seqFrom (RT.java:528)


解决方案是使用 {{(zero? (count (transient ...)))}} 检查。

*原因:* {{empty?}} 基于 seqability,而临时集合没有实现。

*建议:* 向 {{empty?}} 添加一个分支以支持 counted? 集合。临时集合实现了 Counted,因此通过此分支获得支持。其他计数的集合更快。Seq 分支继续对序列工作。

性能测试


(def p [])
(def p1 [1])
(def t (transient []))
(def t1 (transient [1]))

;; 记录这些操作的最后时间
(dotimes [i 20] (time (dotimes [_ 10000] (empty? p))))
(dotimes [i 20] (time (dotimes [_ 10000] (empty? p1))))
(dotimes [i 20] (time (dotimes [_ 10000] (empty? t))))
(dotimes [i 20] (time (dotimes [_ 10000] (empty? t1))))


结果

||coll||before||after||result||
|p|0.72 ms|0.08 ms|为空时速度更快|
|p1|0.15 ms|0.13 ms|非空时略有提升|
|t|error|0.19 ms|不再报错|
|t1|error|0.20 ms|不再报错|

不确定是否应该调整文档字符串以使其更通用,特别是“与(not(seq coll))相同”,现在只对 Seqable 有效,但不是 Counted。我认为在那里使用(seq coll)进行序列检查的建议仍然很好。

我快速浏览了其他计数的类型,但不是序列(也不是 Seqable)的,没有发现太多,除了类似 ChunkBuffer 的内部事物。许多既是也是,因此会使用计数路径(例如所有持久性集合和任何类型的 IndexedSeq)。

我认为另一个选项可能是完全切换 empty? 到关于(zero?(bounded-count 1 coll))并且完全依赖于 count 的多态。

*补丁:* clj-1872.patch

5 个答案

0

评论由:alexmiller 提出

可能类似于 CLJ-700。

0

评论由:devn 提出

如 CLJ-700 中所述,这是一个不同的问题。

0

评论由:devn 提出

首先要讨论的是原始描述中提出的 (empty? (transient ()))。根据 https://clojure.org/reference/transients 中的文档说明,在列表上支持 transients 不会带来任何好处。

Java 集合的当前行为

`
(empty? (java.util.HashMap. {}))
=> true

(empty? (java.util.HashMap. {1 2}))
=> false

(seq (java.util.HashMap. {1 2}))
=> (#object[java.util.HashMap$Node 0x4335c9c3 "1=2"])

(seq (java.util.HashMap. {}))
=> nil
`

Java 数组也有相同的行为。

在 CLJS-2802 中,当前补丁通过对 empty? 中的问题使用 cond 来处理,并显式检查它是否是 TransientCollection,如果是,使用 (zero? (count coll)) 作为原始描述中提到的解决方案作为替代办法。

目前,transient 集合没有像持久集合那样实现 Iterable。如果实现了 Iterable,我相信 RT.seqFrom 将会工作,并且由于扩展,empty? 也会工作。

0

评论由:alexmiller 提出

我认为 transient 集合不应是 Seqable 的有很好的理由——seqs 意味着缓存,而缓存会对性能造成伤害,而使用 transient 的全部原因是为了批量加载性能。这似乎是背道而驰的。迭代器是有状态的,而且我怀疑仅仅为了检查 empty? 而添加可能是一个不好的主意。

检查 counted? colls 的空缺性会有涵盖所有的 transient 集合以及其他任何被计数而没有创建 seq 的东西。这可能对所有这些情况都更快,并且不需要在实现上对任何新的人有所要求。

另一个选择是有一个 IEmptyable 接口和/或协议来显式地指示 empty? 检查支持。可能有点过度。

0
参考资料: https://clojure.atlassian.net/browse/CLJ-1872 (由 alex+import 汇报)
...