In my practice, using three-arities of less/greater operations is pretty common for e.g. checking a number is in range:
(< 0 temp 100)
The problem is, it is almost three times as slow compared to {{(and (< 0 temp) (< temp 100))}}.
This happens because three-arities are handled by the generic vararg arity branch:
(defn <
"Returns non-nil if nums are in monotonically increasing order,
otherwise 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)
This patch adds special handling for three-arities to these fns: {{< <= > >= = == not=}}
(defn <
"Returns non-nil if nums are in monotonically increasing order,
otherwise 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 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)))
The performance gains are quite significant:
(= 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%)
Higher arities also become faster, mainly because there's one less iteration now:
(= 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%)
This patch also changes vararg artity of {{not=}} to use next/recur instead of {{apply}}:
(defn not=
"Same as (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)))
Results are good:
(not= 1 2 3 4) 130.517439 ns => 29.675640 ns (-77%)
I'm also doing what [~wagjo] did in CLJ-1912 (calculating {{(next more)}} just once), although perf gains from that alone are not that big.
My point here is that optimizing three-arities makes sence because they appear in the real code quite often. Higher arities (4 and more) are much less widespread.