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直接调用-Reduce(IReduce的-of)。从而绕过了函数r/reduce中对nil的特殊情况处理。

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

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

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

18 答案

0投票
by

由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投票
by
由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、数组和对象类型而减慢其他随机代码。不过,我不确定究竟应该测试什么。

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


一些最后得到的结果是

第一个补丁:75680毫秒
第二个补丁: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的一个错误: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投票
by

评论由:dnolen发表

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

0投票
by

由jdevuyst发表的评论:

我使用 {{cljs}} 和 {{"{:optimizations :advanced}"}} 对相同的代码(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投票
by

由jdevuyst发表的评论:

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

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

此外,这个补丁也修复了数组中的 {{r/fold}}。

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

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

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

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

0投票

评论由:dnolen发表

如果基准测试解释了修改补丁前后的性能,那么它们将更具信息量。

0投票

由jdevuyst发表的评论:

{{r/reduce}} 之前不能对 nil 或 JavaScript 数组工作。

我没有推荐补丁的一个原因是我不知道你希望优化哪个用例。

...