Derek Peirce
12/10/2020, 4:37 AMpublic inline fun <T1, T2: Comparable<T2>> compareBy(crossinline selector: (T1) -> T2): Comparator<T1> =
Comparator { a, b -> selector(a).compareTo(selector(b)) }
This looks similar to the existing `compareBy`:
public inline fun <T> compareBy(crossinline selector: (T) -> Comparable<*>?): Comparator<T> =
Comparator { a, b -> compareValuesBy(a, b, selector) }
But the type ambiguity and nullability make it inefficient, as it needs to box primitives. I ran a benchmark comparing the two compareBy
implementations for finding the maximum of100,000 objects by their random int fields, and got these results:
Benchmark (N) Mode Cnt Score Error Units
ComparatorBenchmarkPrimitiveHelper.maxKt 100000 avgt 10 2.824 ± 0.102 ms/op
ComparatorBenchmarkPrimitiveHelper.maxPrimitive 100000 avgt 10 0.381 ± 0.155 ms/op
The results speak for themselves, the mass boxing and unboxing in the existing implementation has major performance implications and ought to be prevented with a more specialized compareBy
method.
This naturally extends to the methods that use it such as sortBy
and sortedBy
(I ran the benchmark using maxBy
because it gave the most contrast, sortBy
spent more time on intermediate operations). Strangely, maxBy
and similar already require non-null values and work as they are.ilya.gorbunov
12/10/2020, 6:04 AMcompareBy
with a constrained selector, considering mainly the benefit of more type safety than more performance, and found that introducing an additional type parameter might be unfortunate for some quite common usages of compareBy
.
https://youtrack.jetbrains.com/issue/KT-34043#focus=Comments-27-3715015.0-0Derek Peirce
12/10/2020, 4:51 PMilya.gorbunov
12/10/2020, 8:13 PM