请分享您的想法,参加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直接调用-reduce (of IReduce)。因此,跳过了函数r/reduce中对nil的特殊情况。

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

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

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

18 个答案

0

评论由:jdevuyst

一个替代补丁,其中r/reduce和r/fold将数组和非空作为特殊情况处理 -- 与数组和非空实现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、数组、对象目前)被视为特殊情况,则{{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、数组和对象类型而使其他随机代码变慢。但我不确定我到底应该测试什么。

(注意,第二个和第三个补丁也包括对{{Cat}}的修复,并包括更多的单元测试。建议不要直接使用第一个补丁。)

0

评论由:dnolen

是的,你的测试太多了,包括{{vec}}、{{range}}、懒序列。也测试了小N。看看Mori README中的reducers示例

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

这仍然是一个糟糕的时间测量方法,你正在记录范围和 seq' 的成本。使用 {{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 里的一个bug:[链接](https://groups.google.com/forum/#!searchin/clojure-dev/kv-reduce/clojure-dev/bEqECvbExGo/iW4B2vEUh8sJ)。我提出使用宏(或复制粘贴)以避免补丁2中的额外函数调用,这也可能修复这个不一致性。

0

评论由:dnolen

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

0
by

评论由:jdevuyst

我使用了repljs(Rhino?)。我将在明天在一个更真实的环境中进行测试。

0
by

评论由:dnolen

是的,使用Rhino进行基准测试不具有参考意义。

0
by

评论由: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
by

评论由:jdevuyst

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

这个补丁修复了{{r/Cat}}的问题。(该问题在第二个和第三个补丁中也有所解决。包括单元测试。)

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

总结来说,需要在这几个补丁之间做出选择。

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

实现细节上,patch-1-redux的精神更接近Clojure源代码。alt补丁的精神更接近ClojureScript源代码。

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

0

评论由:dnolen

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

0

评论由:jdevuyst

{{r/reduce}}以前不支持nil或JavaScript数组。

我难以推荐补丁的原因之一是我不清楚你想要针对哪种用例进行优化。

...