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之外,不应支持循环/recur。假设这是正确的,这将是一种预期行为。

最近一次重大的数值/原始重构决策只支持原始long和double。结果是,原始int循环难以优化 - 当我在紧循环中与Java互操作工作时光我遇到这种情况,例如当与(例如)字符串、集合或数组操作(所有这些都是int索引)时。

关于unchecked-inc与unchecked-inc-int,主要不是性能,而是行为的原因。特别是,哈希操作通常期望得到32位int溢出语义,而不是64位int溢出语义。

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

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本身的这个例子似乎假设整型应该可以在循环中使用:https://clojure.org/reference/java_interop#primitives

同一段落中
“所有Java原始类型都受支持”
let/loop-bound本地变量可以是原始类型,其初始化表达式的推断类型可能是原始类型
“”

0

评论者:alexmiller

我同意应该能够将其他类型的原始数据让绑定 - 我真正谈论的是recur时发生的事情。

对于fn recur目标,您期望会发生什么?我不期望使用原始数据类型long或double之外的其他类型,因为它们不能出现在函数签名中。

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

0

评论者:alexmiller

我确实遇到过在紧密的interop循环中创建原始int循环/recur很有用的情况,考虑到在Java中使用int索引是多么常见(特别是alioth基准测试中的某些基准测试会从中受益)。我认为对于float的论据要弱得多。

0

评论者:bronsa

鉴于在那些位置唯一可能的原始绑定是long或double,并且int/float无论如何都会被扩展,所以我们不需要担心fn recur目标,但是,这个工单补丁中的测试确实需要确保这种情况得到了妥善处理。

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

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

0

评论者:alexmiller

我会在这里留这样一个问题,但是我 将将其视为对当前行为的增强。我认为有很好的机会,Rich将会说这就是预期的行为。

但我认为这个例子不是很好的例子,并欢迎更好的例子。关于unchecked-inc-int的保留似乎不正确或不合理(因为应该适用于长的,而且它并不设计用于性能原因)。一个很好的例子应该展示在循环中使用Java API,其中int索引和int计算给出了更好的性能。

0

评论者:bronsa

我已经编辑了标题,因为错误在 loop 中,而不是 recur

0
评论由:bronsa_ 提出

附上一个补丁,该补丁移除了编译器在循环绑定上执行的无用扩展,以下是一个在整型数组上执行累加操作的加速效果基准。


在补丁之前

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热修补已经更新到master,没有改变,保留了归属

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