# Add three-arities to < <= > >= = == not=

in Clojure
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}
([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}
([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
: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.

by

Benchmark code here https://gist.github.com/tonsky/442eda3ba6aa4a71fd67883bb3f61d99

by

It might make more sense to combine this with CLJ-1912, otherwise these patches will fight.

by

Use this patch if CLJ-1912 would be applied first

by

I found a problem with previous patches that during defining {{=}} (equality), {{and}} is not yet defined. Replaced with {{if}}

by

Dupe of CLJ-1912

by

[~alexmiller] It is a duplicate, but my patch is waaaaaaaaay faster. Just look at the numbers (70-80% improvement vs 5-10%). It’s because I introduced a real arity so that intermediate collection is not created and is not destructured in case of 3 arguments.
by