在我的实践中,使用小于/大于的三元操作数非常常见,例如检查一个数字是否在范围内。
(< 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 替换了它。