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

欢迎!请参阅关于页面以了解更多关于如何工作的信息。

0
ClojureScript
  1. 目前这不起作用

(->> nil

 (r/map identity)
 (r/reduce + 0))
 ; org.mozilla.javascript.JavaScriptException: Error: No protocol method IReduce.-reduce defined for type null

这是因为由r/reducer或r/folder创建的reducer直接调用-reate(IReduce的-of)。因此,它们绕过了r/reduce中对nil的特殊情况。

  1. 对于数组类型的集合也存在一个完全类似的问题。

  2. 补丁CLJS-700错误地将coll-fold定义为了clamp.core/IPersistentVector类型。这应该是clamp.core/PersistentVector。(在ClojureScript中不存在IPersistentVector协议。)

我将很快附上一个补丁,通过实现nil和数组类型的IReduce来解决上述所有问题。该补丁还包括单元测试。

18 答案

0

评论人:jdevuyst

一个替代补丁,其中r/reduce和r/fold将数组和nil作为特殊情况处理 -- 相比于让数组和nil实现IReduce和CollFold。

r/reducer、r/folder和r/Cat的协议方法现在调用r/reduce和r/fold,而不是直接调用-reduce和coll-fold。

该补丁还修复了Cat的coll-fold实现中的一个错误,该错误之前使用(reducef)而不是(combinef)作为初始值。新代码是从Clojure实现中复制粘贴的,并使用了fork-join占位符。

0
_评论人:dnolen_

第二个补丁中的{{implements?}}可能应该是{{satisfies?}}。您已经对该补丁前后的性能进行了基准测试了吗?
0
_评论区:jdevuyst_

如果我的理解正确,那么 {{(satisfies? x y)}} 约等于 {{(or (implements? x y) (natively-satisfies? x y))}}。

如果原生类型(nil、array、对象)被当作特殊情况对待,那么 {{implements?}} 看起来更合适。

但 {{satisfies?}} 也可以工作,因此我附加了一个新的 'alt' 补丁。
0

评论人:jdevuyst

第一个补丁实际上在运行以下代码时更快

(time (->> (repeat 1000 (vec (range 1000)))
           vec
           (r/mapcat identity)
           (r/map inc)
           (r/filter even?)
           (r/fold +)))

这需要大约700毫秒。使用第一个补丁可以快100-300毫秒。这是经过重复(但非正式)测试后的结果。

我想担心的是,第一个补丁可能会因为涉及到扩展 nil、array 和对象类型而减慢其他随机代码。但我不确定具体应该测试什么。

(请注意,第二个和第三个补丁也包含对 {{Cat}} 的修复,并包含了更多单元测试。最好不要直接应用第一个补丁。)

0

评论区:dnolen

你的计时太过多了,包括 {{vec}}、{{range}}、延迟序列。也测试了一个小 N。看看 Mori README 上的 reducers 示例 - https://github.com/swannodette/mori。谢谢。

0
_评论区:jdevuyst_

我尝试运行以下代码


(let [coll (vec (repeat 1000 (vec (range 10))))]
  (time (doseq [n (range 1000)]
               (->> coll
                    (r/mapcat identity)
                    (r/map inc)
                    (r/filter even?)
                    (r/fold +)))))


我得到的一些最后结果是

第1个补丁:75680毫秒
第2个补丁:76585毫秒

说实话,虽然第一个补丁似乎大多数时候都是赢家,但有时第二个补丁更快。

我还尝试了从第二个补丁中移除 {{implements?}}/{{satisfies?}} 的检查,并覆盖类型 {{object}} 的 coll-fold 协议方法(如第一个补丁中所示)。这种“混合”方法通常(但不是总是)会导致速度减慢。

我不确定我应该如何进行。也许我应该同时运行这两个补丁几分钟?
0

评论区:dnolen

这仍然是一个错误的定时方式,你在记录范围和顺序的成本。请使用{{dotimes}}。

0

评论人:jdevuyst

嘿,我认为懒惰的序列确实导致了很多分配。

好吧,我重新编写了我的测试并运行了多次。我还同时在向量和数组上进行了测试。

补丁1需要轻微调整。当coll-fold被调用时,补丁1只为类型{{object}}指定了一个后备方案(即调用{{r/reduce}})。我必须为类型{{array}}添加相同的后备方案。(这很奇怪!)

所以这里是结果。

对于向量

`
(let [coll (vec (repeat 100 (vec (range 100))))]
(time (dotimes [n 3000]

      (->> coll
          (r/mapcat identity)
          (r/map inc)
          (r/filter even?)
          (r/fold +)))))

`

补丁1:205872毫秒
补丁2:210756毫秒

对于数组

`
(let [coll (into-array (repeat 100 (into-array (range 100))))]
(time (dotimes [n 3000]

      (->> coll
          (r/mapcat identity)
          (r/map inc)
          (r/filter even?)
          (r/fold +)))))

`

补丁1:123567毫秒
补丁2:119704毫秒

我多次运行了我的测试,结果相当一致。补丁1对向量更快,补丁2对数组更快。

这很有道理。

在补丁1中,{{reducer}}将直接调用{{-reduce}}。在补丁2中,{{reducer}}首先调用{{r/reduce}},如果集合是向量就调用{{-reduce}},如果是数组就调用{{array-reduce}}。因此,补丁2在向量的情况下包含了一个额外的函数调用,但在数组的情况下避免了在本地类型上调用协议方法。

使用宏(或复制粘贴)可以避免这个多余的函数调用。这值得尝试吗,还是保持代码清洁更重要?

--

我刚刚意识到,补丁2在语义上与Clojure略有不同,尽管这可能是在Clojure中的错误: https://groups.google.com/forum/#!searchin/clojure-dev/kv-reduce/clojure-dev/bEqECvbExGo/iW4B2vEUh8sJ。我建议使用宏(或复制粘贴)以避免补丁2中的额外函数调用,也可能解决这个问题不一致性。

0

评论区:dnolen

你是如何进行这些基准测试的?使用V8、JavaScriptCore、SpiderMonkey?在浏览器中?优化设置等。

0

评论人:jdevuyst

我使用了repljs(Rhino?)。我明天将在更真实的环境中再次测试。

0

评论区:dnolen

是的,使用Rhino进行基准测试并不是很有信息量。

0

评论人:jdevuyst

我使用{{cljs}}编译了相同的代码(n=3000),并使用{{"{:optimizations :advanced}"}}。

然后我在Firefox、Chrome和Safari的最新稳定版本中测试了它。我关闭了所有的浏览器。对于每个浏览器,我都遵循以下步骤

  • 打开浏览器
  • 打开开发者控制台
  • 运行补丁1的基准测试
  • 运行补丁2的基准测试
  • 运行补丁1的基准测试并记下结果
  • 运行补丁2的基准测试并记下结果
  • 关闭浏览器

Firefox
- 补丁1. 向量: 26057 毫秒
- 补丁1. 数组: 25026 毫秒
- 补丁2. 向量: 26258 毫秒
- 补丁2. 数组: 36653 毫秒
- 总结:补丁1对于向量和数组都更快

Chrome
- 补丁1. 向量: 7804 毫秒
- 补丁1. 数组: 7092 毫秒
- 补丁2. 向量: 7754 毫秒
- 补丁2. 数组: 6768 毫秒
- 总结:补丁2对于向量和数组更优

Safari
- 补丁1. 向量: 167230 毫秒
- 补丁1. 数组: 108780 毫秒
- 补丁2. 向量: 173940 毫秒
- 补丁2. 数组: 110012 毫秒
- 总结:补丁1对于向量和数组都更快

我不确定这个问题该怎么看。

0

评论人:jdevuyst

我已经附上了第一个补丁的新版本。

这个补丁解决了与{{r/Cat}}有关的问题。 (这个问题在第二个和第三个补丁中也得到了解决。包含一个单元测试。)。

这个补丁还修复了数组的{{r/fold}}。

总结来说,需要在这几个补丁中选择一个。

  • CLJS-736-patch-1-redux.patch
  • CLJS-736-alt.patch (使用 implements?) / CLJS-alt-satisfies.patch (使用 satisfies?)

补丁-1-redux的实现细节与Clojure源代码的精神更加相似。alt补丁与ClojureScript源代码的精神更为相似。

如上所述,alt补丁在语义上与原始Clojure源代码略有不同——但尚不清楚哪种行为是“正确的”。

0

评论区:dnolen

如果基准测试能解释该补丁前后的性能变化,那么信息量会更大。

0

评论人:jdevuyst

{{r/reduce}}之前不适用于nil或JavaScript数组。

我难以推荐补丁的一个原因是,我不知道您想为哪种用例进行优化。

...