请在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 发表评论:

我认为在loop/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)我认为这不正确,因为据我所知,原始类型只应该限制在函数参数/返回值中使用。
注意,例如,char、byte、boolean、short等在let和loop中都可以正常运行,而int和float在let中可以正常运行,但在loop中不行。

这由以下四个Compiler.java文件中的行引起 https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L6278-L6281

根据我了解的情况,目前这四行在这个时间点没有必要存在,移除它们可以使int和float的局部变量在循环中正确生成。

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

同一段话中
{quote} 所有Java原始类型都受支持
let/loop绑定的局部变量可以是原始类型,具有它们的init-form推断的,可能是原始的类型
{quote}

0

由 alexmiller 发表评论:

我同意应该能够让其他类型的原始类型有let绑定,我真的在谈论recur会发生什么。

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

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

0

由 alexmiller 发表评论:

我确实遇到过在紧密交互循环中创建原始int循环/recur非常有用的案例,考虑到int索引在Java中的常见性(alioth基准测试中的部分尤其受益于此)。我认为对于float的论点要弱得多。

0

评论者:bronsa

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

关于floats - 我还记得人们抱怨当使用clojure进行图形处理时对floats的支持不好。

即便承认这是一个薄弱的论点,我始终认为我们应尽可能保持一致性。我认为没有人会期望 let/loop 的局部变量会有不同的行为,或者原始类型(除了关于 long/double 是 fn 参数/返回值唯一有效原始类型的文档限制外)之间有差异。

0

由 alexmiller 发表评论:

我会保留这段内容,但我会将其视为对当前行为的增强。我认为Rich可能会说这是按预期工作的。

尽管如此,我认为这个示例并不十分好,欢迎更好的示例。关于 unchecked-inc-int 的保留似乎对我来说并不正确或有效(因为对该 long 类型的使用应该是良好的,并且它并不专为性能原因而设计)。一个好的例子应该在循环中使用 Java API,其中 int 索引和 int 运算可以提供更好的性能。

0

评论者:bronsa

我把标题编辑了一下,因为这个问题是在 loop 而不是在 recur 中。

0
_评论由:bronsa_ 提供

附上一个补丁,移除了编译器循环绑定上所做的无用扩展,以下是一个在 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微秒
    执行时间标准差:20.292809微秒
   执行时间下四分位数:926.331747微秒 ( 2.5%)
   执行时间上四分位数:1.009189毫秒 (97.5%)
                   开销:1.840681纳秒

在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微秒
    执行时间标准差:13.100790微秒
   执行时间下四分位数:852.357513微秒 ( 2.5%)
   执行时间上四分位数:896.531529微秒 (97.5%)
                   开销:1.844045纳秒

在60个样本中发现1个异常值(1.6667%)
    低严重      1 (1.6667 %)
 异常值方差:1.6389 %,由于异常值,方差略微扩大
nil
0
_by

由 alexmiller 发表评论:

-2 补丁已重新合并到 master,没有变化,保留了归属信息

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