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

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

在最后一次主要的数值/原始重构中,决定只支持原始 long 和 double。这的后果之一是原始 int 循环难以优化——我遇到这种情况最常见的时机是当在字符串、集合或数组操作(所有这些都是 int 索引)中进行与 Java 互操作的紧密循环时。

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

总之,我认为在所给的示例中,我不会使用 int 或 unchecked-inc-int 编写它,如果您在寻找最佳性能,我将使用 long 和 unchecked-inc。

0

回复者:bronsa

(链接:~alexmiller)在我看来这并不正确,因为据我所知,仅在函数的参数/返回值中,原始类型应该被限制为长(longs)和双精度浮点数(doubles)。
请注意,例如,字符(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

评论作者:alexmiller

我同意应该可以声明其他类型的 let-bound 原始类型 - 我真正讨论的是 recur 发生的情况。

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

请注意,我没有关闭此票据,仍在讨论这个问题。

0

评论作者:alexmiller

我确实遇到过制作原始 int loop/recur 提供有用功能的案例,考虑到 Java 中索引的普遍性(尤其是特定 alioth 基准测试将从这一点受益)。我认为对于 float 的争论相对较弱。

0

回复者:bronsa

鉴于在 recur 目标中只有长(long)或双精度浮点数(double)可能的原始绑定,以及 int/floats 无论如何都会被提升,因此我们认为不需要担心 fn recur 目标,但由于这是一个很好的观点,此票据补丁中的测试需要确保确实处理了该情况。

关于 float 的评论 - 我记得有人抱怨 clojure 用于图形处理时对 float 的支持不佳。

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

0

评论作者:alexmiller

我会保留这一点,但我将把它视为对当前行为的增强。我认为Rich可能会说这就是预期行为。

然而,我认为提供的例子并不太好,并欢迎一个更好的例子。关于unchecked-inc-int的保留似乎不正确或无效(因为在long上的使用应该是没有问题的,并且它并不是为了性能而设计的)。一个好的例子应该是使用Java API在循环中,其中int索引和int数学运算能带来更好的性能。

0

回复者:bronsa

我已将标题修改为bug出在loop中,而不是recur中。

0
_由:bronsa_发表的评论

附上一个补丁,该补丁删除了编译器在循环绑定中进行的无用扩宽,以下是当在int数组上进行reducing操作时速度提升的基准示例。


补丁前

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
by

评论作者:alexmiller

-2补丁已重新合并以应用到master,无更改,保留归属

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