参与2024年Clojure状态调查!分享您的看法。 点击此处

欢迎!有关此工作用的更多信息,请参阅关于页面。

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


(< 0 temp 100)


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

这是因为三元操作数由通用的可变参数arity分支处理。


(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 ns => 33.725097 ns (-33%)


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


(defn not=
  "等同于 (not (= obj1 obj2))"
  {:tag Boolean
   :added "1.0"
   :static true}
  ([x] 错误)
  ([x y] (不等于 x y))
  ([x y z] (不等于 x y z))
  ([x y z & more]
   (如果等于 x y)
     (let [nmore (next more)]
        (if nmore
             (recur y z (first more) nmore)
         (不等 y z (more 的第一个)))
    true)


结果良好


(不等 1 2 3 4)      130.517439 纳秒 > 29.675640 纳秒 (减少 77%)


我这里的意思是,对三参数进行优化是有意义的,因为它们在真实代码中相当常见。高阶参数(4个或更多)分布得较少。

这里的问题是,在定义等于运算符(=)和 and 时,and 运算符尚未定义。用 if 替换了它。

9 条回答

0

评论作者:tonsky

基准代码在此:https://gist.github.

0

评论作者:alexmiller

将其与 CLJ-1912 结合可能更合理,否则这些补丁可能会相互斗争。

0

评论作者:tonsky

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

0

评论作者:tonsky

我在之前的补丁中发现了一个问题:在定义等于(=)和 and 时,and 运算符尚未定义。用 if 替换了它。

0

评论作者:alexmiller

CLJ-1912 的重复内容

0
_评论作者:tonsky_

[~alexmiller] 这是一个重复项,但我的补丁要快得多。只需看看数据(相比于5-10%,提高了70-80%)。这是因为我引入了真正的arity,因此在处理3个参数的情况下不会创建或解构中间集合。
0

评论者:wagjo

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

0
_评论作者:tonsky_

[~wagjo] 感谢指出这一点!附上更新后的路径。由于性能提升并非来源于额外的比较,而是由于避免了调用未知arity的函数的开销,所以基准也不是太差。
0
参考:[https://clojure.atlassian.net/browse/CLJ-2075](https://clojure.atlassian.net/browse/CLJ-2075)(由tonsky报告)
...