2024 Clojure状态调查!中分享您的想法。

欢迎!请查看关于页面,了解更多关于如何使用本站的信息。

+4投票
编译器

以下是一个示例

`
(defn incrementer [n]
(let [n (int n)]

(loop [i (int 0)]
  (if (< i n)
    (recur (unchecked-inc-int i))
    i))))

`

在该循环中,循环局部变量开始是一个int类型,但在recur时被扩大到long类型。应该在recur上保留原始的int(或者交替地float)类型,而不仅仅是将其扩大为long(或double)。

将int类型提升为其他类型的编译器代码似乎是:
https://github.com/clojure/clojure/blob/a19c36927598677c32099dabd0fdb9d3097df259/src/jvm/clojure/lang/Compiler.java#L6377-L6380

建议:移除循环绑定中的无用扩大

补丁:clj-1905-2.patch

审核:由Alex Miller审核。我主要的疑问是:我们是否需要支持原始的int/float循环变量?

10 回答

0投票

评论由:alexmiller发表

我认为除了原始的long或double之外,不应该在loop/recur中支持任何类型。假设这是正确的,这将是一个预期的行为。

在最近的一轮主要的数字/原始重构中,我们做出了只支持原始long和double的决定。其结果是,原始的int循环难以优化 - 我遇到这种情况主要是当在Java互操作中在循环(例如)String、collection或array操作时(所有这些都是int索引的)。

关于unchecked-incunchecked-inc-int,有两个变体的主要原因不是性能而是行为。特别是,散列操作往往期望获得32位int溢出语义,而不是64位int溢出语义。

总结来说,我认为在提供示例中,我不会用int或unchecked-inc-int编写,而是用long和unchecked-inc来获得最佳性能。

0投票

评论者:bronsa

(链接:~alexmiller)我认为这并不正确,因为根据我所知,原始类型在函数参数/返回值中才应该被限制为长(long)和双精度(double)。
注意,例如,char、byte、boolean、short等在let和loop中使用是没有问题的,而int和float在let中使用是可以的,但在loop中就不行。

这是由于如下 Compiler.java 文件中的这4行造成的 https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L6278-L6281

据我所知,现在在这个时间点这4行没有必要存在,去掉它们会使int和float局部变量在循环内部正确地输出。

clojure.org上的这个例子似乎假设整数在循环中应该工作 https://clojure.org/reference/java_interop#primitives

同样的段落中
所有Java原始类型都受到支持
let/loop-bound locals可以是原始类型,拥有它们的init-form推断出的可能为原始类型的类型
{quote}

0投票

评论由:alexmiller发表

我同意应该可以让其他类型的原始类型有所作为 - 我真正讨论的是recur发生了什么。

对于一个fn recur的目标,你会期望发生什么?我不期望会在这里工作除了long或double之外的任何原始类型,因为它们不能出现在函数签名中。

请注意,我还没有关闭这个工单,我们还在讨论这个问题。

0投票

评论由:alexmiller发表

我确实遇到了一些情况,在紧密的交互式循环中创建原始int循环/recur对于Java中常见的int索引非常有用(尤其是alioth的一些基准测试将从中受益)。我认为对于float的论点就弱得多。

0投票

评论者:bronsa

我认为我们不必担心fn recur的目标,因为那里唯一的可能原始绑定是long或double,int/floats无论如何都会扩展,但好的观点,这个工单补丁中的测试需要确保确实处理了这个情况。

关于float - 我想人们抱怨过clojure在图形处理中使用时的float支持不佳。

虽然这个论点可能有些薄弱,但我始终认为我们应该努力使代码尽可能保持一致性。我不认为任何人会期望let/loop局部变量表现不同,或原始类型之间(除了关于long/double是fn参数/返回值唯一可用prim类型这一文档限制之外)存在差异。

0投票

评论由:alexmiller发表

我会在这里保留这个观点,但我将其视为对当前行为的改进。我认为Rich很可能会说这是按照预期工作的。

尽管如此,我认为示例不太合适,并欢迎更佳的示例。我觉得关于unchecked-inc-int的保留似乎是正确和有效的(因为其应在longs上正常工作,而且并不是出于性能原因设计的)。一个好的示例应该展示在使用Java API循环时,int索引和int数学可以带来更好的性能。

0投票

评论者:bronsa

我已修改标题,因为这个bug在loop中,而不在recur中。

0投票
评论者:bronsa

附上了一个补丁,移除了编译器在循环绑定上完成的无效扩展,以下是在减少int数组时的性能提升的基准测试。


补丁前

Clojure 1.8.0
user=> (use 'criterium.core)
nil
user=> (let [xs (int-array (range 1e6))] (bench (areduce xs i ret 0 (+ ret (aget xs i)))))
评估次数:64260,在60个样本中,每个样本1071次调用。
执行时间平均值:954.009929 µs
执行时间标准差:20.292809 µs
执行时间下限四分位数:926.331747 µs (2.5%)
执行时间上限四分位数:1.009189 ms (97.5%)
使用的开销:1.840681 ns

在60个样本中发现了4个异常值(6.6667%)
    低严重性      4 (6.6667%)
  异常值方差:9.4244%(略微由于异常值而膨胀)
nil


补丁后

Clojure 1.9.0-master-SNAPSHOT
user=> (use 'criterium.core)
nil
user=> (let [xs (int-array (range 1e6))] (bench (areduce xs i ret 0 (+ ret (aget xs i)))))
评估次数:68640,在60个样本中,每个样本1144次调用。
执行时间平均值:870.462532 µs
执行时间标准差:13.100790 µs
执行时间下限四分位数:852.357513 µs (2.5%)
执行时间上限四分位数:896.531529 µs (97.5%)
使用的开销:1.844045 ns

在60个样本中发现了1个异常值(1.6667%)
    低严重性      1 (1.6667%)
 异常值方差:1.6389 % 异常值使方差略有膨胀
nil
0投票

评论由:alexmiller发表

-2补丁已重新合并到master分支,无更改,保留著作权

0投票
参考:[https://clojure.atlassian.net/browse/CLJ-1905](https://clojure.atlassian.net/browse/CLJ-1905) (由reborg报告)
...