2024 年 Clojure 状态调查! 分享您的想法。

欢迎!请查看 关于 页面以了解该机制的一些更多信息。

0
Clojure
在我的实践中,使用小于/大于操作的三参数函数非常常见,例如检查数字是否在范围内。


(< 0 temp 100)


问题是,它比 {{(and (< 0 temp) (< temp 100))}} 慢近三倍。

这是因为三参数函数由通用的参数分支进行处理。


(defn <
  "如果数字是单调递增的,则返回非nil,否则返回false。"
  {:inline (fn [x y] `(. clojure.lang.Numbers (lt ~x ~y)))
   :inline-arities #{2}
   :added "1.0"}
  ([x] true)
  ([x y] (. clojure.lang.Numbers (lt x y)))
  ([x y & more]
    (if (< x y)
     (if (next more)
       (recur y (first more) (next more))
       (< y (first more)))
     false)
     false)


此补丁为此函数添加了对三参数的特殊处理:{{< <= > >= = == not=}}


(defn <
  "如果数字是单调递增的,则返回非nil,否则返回false。"
  {:inline (fn [x y] `(. clojure.lang.Numbers (lt ~x ~y)))
   :inline-arities #{2}
   :added "1.0"}
  ([x] true)
  ([x y] (. clojure.lang.Numbers (lt x y)))
  ([x y & more]
  ([x y z] (and (. clojure.lang.Numbers (lt x y))
                (. clojure.lang.Numbers (lt y z))))
  ([x y z & more]
   (if (< x y)
     (let [nmore (next more)]
       (if nmore
         (recur y z (first more) nmore)
         (< y z (first more))))
     false)))


性能提升非常显著


(= 5 5 5)      24.508635 ns => 4.802783 ns (-80%)
(not= 1 2 3)      122.085793 ns => 21.828776 ns (-82%)
(< 1 2 3)      30.842993 ns => 6.714757 ns (-78%)
(<= 1 2 2)      30.712399 ns => 6.011326 ns (-80%)
(> 3 2 1)      22.577751 ns => 6.893885 ns (-69%)
(>= 3 2 2)      21.593219 ns => 6.233540 ns (-71%)
(== 5 5 5)      19.700540 ns => 6.066265 ns (-69%)


高阶参数处理速度也更快,主要原因是在这里不再有最后一次迭代。


(= 5 5 5 5)      50.264580 ns => 31.361655 ns (-37%)
(< 1 2 3 4)      68.059758 ns => 43.684409 ns (-35%)
(<= 1 2 2 4)      65.653826 ns => 45.194730 ns (-31%)
(> 3 2 1 0)      119.239733 ns => 44.305519 ns (-62%)
(>= 3 2 2 0)      65.738453 ns => 44.037442 ns (-33%)
(== 5 5 5 5)      50.773521 纳秒 → 33.725097 纳秒 (-33%)


此补丁还将 {{not=}} 的 vararg 参数更改为使用 next/recur 而不是 {{apply}}


(defn not=
  "与 (not (= obj1 obj2)) 相同"
  {:tag Boolean
   :added "1.0"
   :static true}
  ([x] false)
  ([x y] (not (= x y)))
  ([x y z] (not (= x y z)))
  ([x y z & more]
   (if (= x y)
     (let [nmore (next more)]
       (if nmore
         (recur y z (first more) nmore)
                        (not= y z (first more))))
     true)))


结果良好


(not= 1 2 3 4)      130.517439 纳秒 → 29.675640 纳秒 (-77%)


我还在做 [~wagjo] 在 CLJ-1912 中所做的事情(仅计算 {{(next more)})一次),尽管仅从这个改动中获得的速度提升并不算很大。

我的观点是,优化三个参数是有意义的,因为它们在真实代码中出现的频率相当高。更高的参数(4个及以上的)分布要少得多。

9 个答案

0
by
0
by

评论者:alexmiller

可能将此与 CLJ-1912 结合起来更为合理,否则这些补丁可能会产生冲突。

0
by

评论者:tonsky

如果先应用 CLJ-1912,请使用此补丁

0
by

评论者:tonsky

我发现前几个补丁存在问题,在定义 {{=}}(等式)时,{{and}} 尚未定义。用 {{if}} 替换。

0
by

评论者:alexmiller

与 CLJ-1912 重复

0
by
_评论者:tonsky_

0
by

评论者:wagjo

在提供的补丁中有一个相当严重的bug,导致例如 {{(= 3 3 2)}} 返回true。因此,我认为基准测试也存在问题。

0
已回答 by
...