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循环难以优化——我最常遇到这种情况是在与Java互操作相关的紧密循环中,例如进行字符串、集合或数组操作(所有这些都是int索引)。

关于un-checked-inc和un-checked-inc-int,拥有这两种变体的主要原因不是性能,而是行为。特别是,散列操作通常期望得到32位int溢出语义,而不是64位int溢出语义。

总之,我认为在给出的示例中,我不会用int或unchecked-inc-int,而会使用long和unchecked-inc来编写它,如果您正在寻找最佳性能。

0
by

评论人为:bronsa

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

这是由以下编译器.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局部变量可以是原始类型,具有它们的init-form推断的可能的原始类型
{quote}

0
by

评论者:alexmiller

我同意其他类型的原始类型let-bound应该可行——我真正讨论的是recur发生了什么。

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

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

0
by

评论者:alexmiller

我确实遇到过创建原始int loop/recur在tight interop循环中有用的情况,因为int索引在Java中非常常见(特别是某些alioth基准测试将从中受益)。我认为对于float的论点要弱得多。

0
by

评论人为:bronsa

鉴于在该处只能有long或double的原始绑定,并且int/float会自动升级,我认为我们不需要担心fn recur目标,但是,此ticket的补丁测试需要确保该案确实处理了。

关于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)))))
评估次数:60样本中的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样本中的68640次。
   平均执行时间:870.462532 µs
标准偏差执行时间:13.100790 µs
下四分位数执行时间:852.357513 µs ( 2.5%)
上四分位数执行时间:896.531529 µs (97.5%)
                   开销使用:1.844045 纳秒

在60个样本中发现1个异常值(1.6667%)
    低严重性…      1(1.6667%)
 异常值的偏差:1.6389% 偏差略微被异常值所膨胀
nil
0

评论者:alexmiller

-2 补丁重 bases 以应用主分支,没有变化,保留归属性

0
参考: https://clojure.atlassian.net/browse/CLJ-1905 (由 reborg 报告)
欢迎来到 Clojure Q&A,您可以在此提问并从 Clojure 社区成员那里获得答案。
...