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、集合或数组时(所有这些都是 int 索引)。

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

总之,我认为在所提供的示例中,我不会使用 int 或 unchecked-inc-int,而是使用 long 和 unchecked-inc,如果您在寻求最佳性能。

0
by

评论由: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上的这个例子本身似乎假设int应该在循环中使用 https://clojure.org/reference/java_interop#primitives

同样在那段文字中
“所有Java原始类型均受支持”
let/loop-bound局部变量可以是原始类型,具有它们的init-form推断,可能为原始类型
“”

0
by

评论者:alexmiller

我同意应该可以让其他类型的原始类型绑定到let - 我真正讨论的是 recur中的情况。

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

注意,我没有关闭这个缺陷报告,还在讨论这个问题。

0
by

评论者:alexmiller

我确实遇到过一些情况,其中创建原始整型的loop/recur循环对于紧密互操作循环来说是有用的,因为int索引在Java中很常见(特别是某些alioth基准测试将从中受益)。但我认为对于float来说,这种论点要弱得多。

0
by

评论由:bronsa 制作

我认为我们不需要担心fn recur的目标,因为那里唯一的可能原始绑定是long或double,int/floats在任何情况下都会提升(扩大),但是好,这个缺陷报告的补丁中的测试确实需要确保情况得到处理。

关于floats,我记得有人在用clojure进行图形处理时抱怨float的不当支持。

即使这个论点很弱,我始终认为我们应该尽可能地做到一致。我认为没有人会期望let/loop局部变量有不同的表现,或者原始类型之间的差异(除了关于long/double是fn参数/返回值的唯一工作原始类型的文档限制)。

0

评论者:alexmiller

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

但我认为这个例子并不很好,并期待一个更好的例子。关于unchecked-inc-int的保留似乎不正确或不合理(因为其在long上使用应该是没有问题的,并且并不是为了性能原因而设计)。一个很好的例子应该展示在循环中使用Java API,其中int索引和int数学可以得到更好的性能。

0

评论由:bronsa 制作

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

0
评论者:bronza

附上一个补丁,该补丁移除了编译器在循环绑定时执行的无用扩展,以下是一个基准测试,它展示了当在int数组上使用areduce时获得的加速。


在补丁之前

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)))))
评估次数:在60个样本中的1071次调用中有64260次。
             平均执行时间:954.009929µs
    执行时间标准偏差:20.292809 µs
   执行时间下四分位数:926.331747 µs(2.5%)
   执行时间上四分位数:1.009189 ms(97.5%)
                   开销使用:1.840681 ns

在60个样本中发现了4个异常值(6.6667%)
    low-severe     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)))))
评估次数:在60个样本中的1144次调用中有68640次。
             平均执行时间:870.462532µs
    执行时间标准偏差:13.100790 µs
   执行时间下四分位数:852.357513 µs(2.5%)
   执行时间上四分位数:896.531529 µs(97.5%)
                   开销使用:1.844045 ns

在60个样本中发现了1个异常值(1.6667%)
    low-severe     1(1.6667%)
 异常值方差:1.6389% 变异性略受异常值影响
nil
0
by

评论者:alexmiller

-2 补丁重组以应用到 master,无更改,保留归属权

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