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)
   (if (next more)
   (recur y (first more) (next more))
   (< y (first more)))
   false)


此补丁将这些 fn 的三元性添加了特殊处理:{{< <= > >= = == 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}} 的可变参数转换为使用 next/recur 而不是 {{apply}}


(defn not=
  "与 (not (= obj1 obj2)) 相同。"
  {:tag Boolean
  :added "1.0"}
静态 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 ns => 29.675640 ns (-77%)


我在这里想说明的是,优化三元arity是有意义的,因为它们在真实代码中出现的频率相当高。更高阶的arity(4及以上)出现得较少。

这里提出的请求

by

0 投票
评论人:tonsky

此处是基准代码的链接:https://gist.github.com/tonsky/442eda3ba6aa4a71fd67883bb3f61d99

by

0 投票
评论人:alextiller

可能把这项工作与CLJ-1912结合起来更有意义,否则这些补丁将相互冲突。

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

0 投票
by

此处是基准代码的链接:https://gist.github.com/tonsky/442eda3ba6aa4a71fd67883bb3f61d99

Use this patch if CLJ-1912 would be applied first

0 投票
by

此处是基准代码的链接:https://gist.github.com/tonsky/442eda3ba6aa4a71fd67883bb3f61d99

我在之前补丁定义{{=}}(等价)时发现一个问题,即{{and}}尚未定义。已经将其替换为{{if}}

0 投票
by

可能把这项工作与CLJ-1912结合起来更有意义,否则这些补丁将相互冲突。

CLJ-1912的副本

0 投票
by
评论:由 tonsky 提供

[~alexmiller] 这是一个重复项,但我的补丁要快得多。只需看看这些数字(较之前提高了70-80%,而之前只提高了5-10%)。这是因为我引入了真正的arity,所以当有3个参数时,不会有中间集合的创建和拆除。
0 投票
by

评论:由 wagjo 提供

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

0 投票
by
评论:由 tonsky 提供

[~wagjo] 感谢指出这个问题!附上一个更新的路径。但由于性能提升并非来自指数更少或更多的一次比较,而是一点没有调用未知arity的函数的开销,所以基准测试也没有多大的缺陷。
0 投票
by
参考:[https://clojure.atlassian.net/browse/CLJ-2075](https://clojure.atlassian.net/browse/CLJ-2075)(由 tonsky 报告)
...