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,有两个变种存在的主要原因不是性能而是行为。特别是,hashing 操作通常期望获得 32 位整型溢出语义,而不是 64 位整型溢出语义。

总之,我认为在给定的示例中,我不会用 int 或 unchecked-inc-int 来编写它,而是用 long 和 unchecked-inc 来寻求最佳性能。

0

评论者:bronsa

(链接:~alexmiller)我认为这并不正确,因为据我所知,原始类型只应该限制在函数参数/返回值中。
请注意,例如char、byte、boolean、short等在let和loop中都运行良好,而int和float在let中运行良好,但在loop中不行。

这是由以下4行Compiler.java造成的 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局部变量可以是原始类型,它们有从init-form推断的可能原始类型
{quote}

0

评论者:alexmiller

我同意其他类型的let-bound原始类型应该是可能的——我真正谈论的是 recur中会发生什么。

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

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

0

评论者:alexmiller

我确实遇到过创建原始int loop/recur循环的情况很有用,因为Java中int-indexing用法非常普遍(特别是alioth基准测试中的某些部分将受益于此)。但我认为float的论证要弱得多。

0

评论者:bronsa

鉴于在该处只有可能绑定long或double作为原始类型,int/float也会被提升,所以我认为我们无需担心fn recur目标,但也许这个补丁为这个工单所做的测试应该确保确实处理了这种情况。

RE: 浮点数 -- 我记得有人抱怨在使用Clojure进行图形处理时对浮点数的支持不好。

尽管这可能是一个微弱的论据,但我一直认为我们应该尽力保持一致性。我认为没有人会期望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%)
    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)))))
评估计数 : 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

评论者:alexmiller

将 -2 补丁应用到 master 上重新基,无变更,保留归属

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