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创建的reducers直接调用- réduire (of IReduce)。因此,它绕过了函数r/reduce中针对nil的特殊情况。

  1. 同样的问题存在于数组类型的集合中。

  2. 修补程序CLJS-700错误地将coll-fold定义为类型cljs.core/IPersistentVector。这应该是cljs.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、object)被视为特殊情况,那么 {{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 和 object 类型而减慢其他随机代码。但我不知道具体应该测试什么。

(注意,第二个和第三个补丁也包含对 {{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 +)))))


我得到的一些最后结果显示

1st patch: 75680 msecs
2nd patch: 76585 msecs

说实话,虽然第一个补丁在大多数时候似乎胜出,但有时第二个补丁更快。

我还尝试了一件事,那就是从第二个补丁中删除了{{implements?}}/{{satisfies?}}检查,并覆盖了协议方法coll-fold来处理类型{{object}},就像第一个补丁那样(见第一个补丁)。这种“混合”方法通常(但不是总是)会导致速度下降。

我不太确定我应该怎么进行。或许我可以同时运行这两个补丁几分钟?
0投票

_由 dnolen 发表的评论_

这仍然是一个糟糕的计时方法,你在记录range和seq'的成本。请使用{{dotimes}}。

0投票

评论由:jdevuyst

嗯。我认为懒惰序列确实会导致许多分配。

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

第一个补丁需要稍作调整。当调用coll-fold时,第一个补丁只为类型{{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 +)))))

`

第一个补丁:205872 毫秒
第二个补丁: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 +)))))

`

第一个补丁:123567 毫秒
第二个补丁:119704 毫秒

我运行了我的测试几次,结果相当一致。第一个补丁对向量更快,第二个补丁对数组更快。

这很有道理。

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

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

--

我刚刚意识到第二个补丁在语义上略与Clojure不同,尽管这可能是Clojure的一个错误:。我的建议是使用宏(或者复制粘贴)来避免第二个补丁中额外的函数调用,也可能解决这个差异。

0投票

_由 dnolen 发表的评论_

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

0投票

评论由:jdevuyst

我使用了 repljs(Rhino?)。我将在明天在一个更实际的设置中再次进行测试。

0投票

_由 dnolen 发表的评论_

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

0投票

评论由:jdevuyst

我使用{{cljs}}编译了相同的代码(n=3000)。我将在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?)

如上所述,alt补丁在精神上与Clojure源代码更相似。而patch-1-redux补丁在精神上与ClojureScript源代码更相似。

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

0投票

_由 dnolen 发表的评论_

如果在补丁前后解释性能,基准测试将更有信息量。

0投票

评论由:jdevuyst

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

我难以推荐补丁的一个原因是我不清楚您想优化哪种用例。

...