请点击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)。因此,它们绕过了函数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
by> by> dnolen
_Comment made by: dnolen_

可能应该将{{implements?}}在第二个补丁中修改为{{satisfies?}}。你在补丁前后运行过任何基准测试吗?
0
by
_由jdevuyst评论:

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

如果原生类型(nil、array、object目前)被视为特殊案例,那么{{implements?}}似乎更合适。

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

评论由: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}}的修复,并包含更多的单元测试。 ideally,第一个补丁不应直接应用。)

0
by

由dnolen评论:

你测量的东西太多,包括{{vec}}、{{range}}、懒序列。另外,测试一个很小的N。请查看Mori README上的reducers示例 - https://github.com/swannodette/mori。谢谢。

0
by
_由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补丁:75680毫秒
2nd补丁: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
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 数组。

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

...