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

欢迎!请参阅关于页面以获取更多关于此功能的信息。

+4
编译器

以下示例中

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

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

`

当loop局部变量只是一个局部变量时,它以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循环难以优化——我遇到这种情况的主要时间是在使用Java交互操作时,在(例如)String、集合或数组操作上进行密集型循环(所有这些都是int索引的)。

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

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

0

由:-bronsa

(链接:~alexmiller) 我认为这是不正确的,因为据我所知,只有在与函数参数/返回值相关的地方,要求基本类型仅限于长整型和双精度浮点数。
请注意,例如,字符、字节、布尔值、短整型等在 let 和 loop 中都能很好地工作,而整型和浮点型在 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

同样从那个段落中
{quote} 所有 Java 基本类型都受支持
let/loop-bound locals 可以是基本类型,其初始形式的推断类型可能是基本类型
{quote}

0

评论者:alexmiller

我同意应该可以 let-bound 其他类型的基本类型 - 我真正在谈论 recur 的情况。

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

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

0

评论者:alexmiller

我确实遇到过在处理 tight interop loops 时创建原始 int loop/recur 的循环非常有用的情况,尤其是在 Java 中普遍使用 int 索引的情况下(特别是 alioth 的某些基准测试会受益于此)。但我认为对于 float,这个论点要弱得多。

0

由:-bronsa

鉴于那里唯一可能的原始绑定要么是长型或双精度型,而 int 和浮点数无论如何都会扩展,因此我认为我们不必担心 fn recur 目标,但是提出的好主意,此次补丁对这个问题的测试需要确保这个情况确实得到了处理。

关于浮点数 - 我记得有人在使用闭包进行图形处理时抱怨过对浮点数的支持不好。

即便这是一个微不足道的论点,我始终主张我们应该尽可能保持一致。我认为没有人会期望 let/loop 局部变量有不同表现,或者原始类型(除了文档中提到的限制,即 long/double 是 fn 参数/返回值唯一工作的原始类型外)之间有差异。

0

评论者:alexmiller

我会将这个消息留在这里,但我会将其视为对当前行为的一项改进。我认为 Rich 可能会认为这是按预期工作的。

然而,我认为这个例子并不理想,并欢迎一个更好的例子。关于 unchecked-inc-int 的保留意见在我来看并不正确或合理(因为它在 long 上使用应该是没有问题的,且并不是出于性能原因设计的)。一个好的例子应该展示了在循环中使用 Java API,其中 int-indexing 和 int-math 提供了更好的性能。

0

由:-bronsa

我已将标题进行了编辑,因为这个错误确实出在 loop 函数上,而不是 recur

0
_由 bronsa 发布的评论

附上一个补丁,该补丁删除了编译器对 loop 绑定的无用扩展,以下是一个基准测试,展示了在 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)))))
评估计数: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次,取自1144个调用中的60个样本。
             执行时间平均值:870.462532 µs
    执行时间标准差:13.100790 µs
   执行时间下四分位数:852.357513 µs (25%)
   执行时间上四分位数: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)(由reborg报告)
...