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 发布

我认为在loop/recur中支持的不应该是int或float,而应该是long和double。

最后一次重大numeric/primitive重构决定仅支持原始long和double。这的一个后果是,原始int循环难以优化 - 我主要在与Java互操作性工作,经常在(例如)String、集合或数组操作等紧密循环中遇到这种情况(所有这些都是int索引)。

关于unchecked-inc vs unchecked-inc-int,有两个版本的主要原因不是性能,而是行为。特别是,哈希操作通常期望以32位int溢出语义而不是64位int溢出语义获得。

总之,我认为在所给示例中,我将以long和unchecked-inc而不是int或unchecked-inc-int来编写代码,如果您正在寻找最佳性能。

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本身这个例子似乎假设int在循环中也应该工作 https://clojure.org/reference/java_interop#primitives

同一段话中的其他内容
所有Java原始类型都受支持
let/loop-bound本地变量可以是原始类型,其推断的可能原始类型为其init-form的原始类型
...

0

评论由:alexmiller 发布

我同意应该可以让let绑定的其他类型的原始类型存在——我真正在谈论的是recur会发生什么。

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

注意,我没有关闭这张票据,还在讨论这个问题。

0

评论由:alexmiller 发布

我确实遇到了一些实例,其中创建原始int loop/recur对于紧耦合的Interop循环非常有用,因为Java中的int索引非常常见(特别是alioth基准测试中的某些测试将从中受益)。我认为float的论点要弱得多。

0

评论由:bronsa

鉴于在fn recur目标处唯一可能的原始绑定要么是long或double,int/float总是会扩展,所以我们不需要担心fn recur目标,但是,对于这个票据的补丁中的测试,需要确保确实处理了这种情况。

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

即使承认这是一个弱论点,但我始终坚持我们应该尽可能地保持一致性。我觉得没有人会期望 let/loop 本地变量有不同的行为,或者原始类型(除了有关 long/double 是 fn 参数/返回值唯一工作的原始类型的文档限制)之间的差异。

0

评论由:alexmiller 发布

我会在这里留下一项,但我会将其视为对当前行为的增强。我认为 Rich 很可能会说这是按预期工作的。

尽管如此,我认为这个例子并不是很好,欢迎一个更好的例子。关于未检查的 inc-int 的保留似乎是错误的或不合理的(因为在使用 longs 上应该是没有问题的,而且它们并不是为了性能而设计的)。一个好的例子应该是使用Java API在循环中,其中 int-indexing 和 int-math 提供更好的性能。

0

评论由:bronsa

我编辑了标题,因为这个错误在 loop 中,而不是在 recur 中。

0
_由 bronsa 发表的评论_

附加了一个补丁,该补丁移除编译器在循环绑定上进行的无效扩展,以下是一个基准测试,展示了在 reduce int-array 时获得的加速效果。


补丁之前

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%)
   低严重      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%)
  严重程度较低     1(1.6667%)
异常值的方差:1.6389% 异常值使方差略膨胀
nil
0

评论由:alexmiller 发布

-2补丁已合并到主版本,无变更,保留归属

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