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 添加

我认为除了原始long或double之外,任何类型都不应支持在loop/recur中使用。假设这是正确的,这将是预期的行为。

在最近一轮主要数值/原始重构中,我们做出了只支持原始long和double类型的决定。这一决定的后果之一是原始int循环难以优化 - 我遇到这种情况的主要时间是在使用Java互操时在密集循环中进行(例如)字符串、集合或数组操作(所有这些都是用int索引的)。

关于未检查的inc与未检查的inc-int,有两个变体的主要原因不是性能,而是行为。特别是,散列操作通常期望获得32位int溢出语义,而不是64位int溢出语义。

总之,我认为在给出的例子中,我会用long,而不是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

同样来自该段落的同一部分
{quote}所有Java原始类型都受支持
let/loop-bound局部变量可以是原始类型,具有初始化表达式的推断原始类型
{quote}

0

评论由:alexmiller 添加

我同意应该可以let其他类型的原始类型 - 我实际在讨论在recur中会发生什么。

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

请注意,我还没有关闭此问题,仍在讨论。

0

评论由:alexmiller 添加

我确实遇到过在Java中的int-indexing很常见的情况下,创建原始int loop/recur循环会很有用的案例。对于float,我认为论据要弱得多。

0

评论人:bronsa

在我看来,我们不需要担心fn recur的目标,因为在那里的唯一可能的原生绑定是long或double,int/float无论如何都会提升,但是好点子,修复这个问题的补丁中的测试需要确保确实处理了这种情况。

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-arrays 进行 reduce 时获得的加速效果的基准测试。


补丁前

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 微秒
    执行时间标准差: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)))))
评估计数:在 60 个样本中,每次 1144 次为 68640。
             平均执行时间: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 补丁rebasing到主分支,无变更,保留归属

0
by
...