请在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

我认为只有原始的longs或doubles应该被支持在loop/recur中。假设这是正确的,那么这将是预期的行为。

上一次主要的数字/原始重构决定了只支持原始的longs和doubles。一个结果是原始的int循环难以优化 - 我遇到这种情况的主要时间是当在(例如)String、集合或数组操作中进行Java交互操作并且每次循环时(所有这些都是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 中的 4 行代码造成的 https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L6278-L6281

据我所知,现在没有理由保留这 4 行代码,而且移除它们可以使 int 和 float 局部变量在循环中正确输出

clojure.org 上的这个示例似乎假设整数应该在循环中使用 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 loop/recur 的情况很有用,特别是在给定的 Java 中 int 索引非常普遍的情况下进行紧密的互操作循环(特别是 alioth 基准测试中的某些测试将从中受益)。不过,我认为对于 float 的论点则较弱。

0

评论由:bronsa

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

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

即使这是一个很弱的论点,我始终认为我们应该尽可能保持一致性。我不认为任何人会期望 let/loop 本地变量有不同的行为,或者原始类型(除了 long/double 是 fn 参数/返回值唯一有效的基本类型)之间的差异。

0
by

评论由:alexmiller

我将在这里保留这条信息,但我会将其视为对当前行为的增强。我认为 Rich 会说这完全是按照预期工作的。

我认为示例并不太好,欢迎使用更好的示例。关于 unchecked-inc-int 的保留似乎并不正确或不合理(因为 long 类型上应该性能参数是平衡的)。一个好的例子应该展示在循环中使用 Java API 时,int-indexing 和 int-math 能提供更好的性能。

0
by

评论由:bronsa

我已经编辑了标题,因为问题出在 loop,而不是 recur

0
by
_Comment made by: 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)))))
Evaluation count : 64260 in 60 samples of 1071 calls.
             Execution time mean : 954.009929 µs
    Execution time std-deviation : 20.292809 µs
   Execution time lower quantile : 926.331747 µs ( 2.5%)
   Execution time upper quantile : 1.009189 ms (97.5%)
                   Overhead used : 1.840681 ns

Found 4 outliers in 60 samples (6.6667 %)
    low-severe     4 (6.6667 %)
 Variance from outliers : 9.4244 % Variance is slightly inflated by outliers
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)))))
Evaluation count : 68640 in 60 samples of 1144 calls.
             Execution time mean : 870.462532 µs
    Execution time std-deviation : 13.100790 µs
   Execution time lower quantile : 852.357513 µs ( 2.5%)
   Execution time upper quantile : 896.531529 µs (97.5%)
                   Overhead used : 1.844045 ns

Found 1 outliers in 60 samples (1.6667 %)
    low-severe     1 (1.6667 %)
 异常值方差:1.6389 % 异常值使方差略有膨胀
nil
0

评论由:alexmiller

-2 补丁已经重新基础到主分支,无变化,保留归属

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