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和数组类型的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_创建

第二个补丁中,也许应该使用{{satisfies?}}而不是{{implements?}}。你在补丁前后执行过任何基准测试吗?
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 示例 - 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

这种方法进行时间测量仍然不好,你正在记录范围的成本和序列化。请使用 {{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](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?

实现细节中,patch-1-redux 在精神上更类似于 Clojure 源代码。alt 补丁在精神上更类似于 ClojureScript 源代码。

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

0

评论者:dnolen

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

0

评论由:jdevuyst创建

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

我难以推荐一个补丁的一个原因是,我不知道你的用例你希望优化什么。

...