Рассмотрим более простой случай, не связанный Цирцея или общий вывод на все:
package demo
import org.openjdk.jmh.annotations._
@State(Scope.Thread)
@BenchmarkMode(Array(Mode.Throughput))
class OrderingBench {
val items: List[(Char, Int)] = List('z', 'y', 'x').zipWithIndex
val tupleOrdering: Ordering[(Char, Int)] = implicitly
@Benchmark
def sortWithResolved(): List[(Char, Int)] = items.sorted
@Benchmark
def sortWithVal(): List[(Char, Int)] = items.sorted(tupleOrdering)
}
На 2,11 на моем настольном компьютере я получаю это:
Benchmark Mode Cnt Score Error Units
OrderingBench.sortWithResolved thrpt 40 15940745.279 ± 102634.860 ps/s
OrderingBench.sortWithVal thrpt 40 16420078.932 ± 102901.418 ops/s
И если вы посмотрите на распределения, разница немного больше:
Benchmark Mode Cnt Score Error Units
OrderingBench.sortWithResolved:gc.alloc.rate.norm thrpt 20 176.000 ± 0.001 B/op
OrderingBench.sortWithVal:gc.alloc.rate.norm thrpt 20 152.000 ± 0.001 B/op
Вы можете сказать, что происходит с разрушением reify
:
scala> val items: List[(Char, Int)] = List('z', 'y', 'x').zipWithIndex
items: List[(Char, Int)] = List((z,0), (y,1), (x,2))
scala> import scala.reflect.runtime.universe._
import scala.reflect.runtime.universe._
scala> showCode(reify(items.sorted).tree)
res0: String = $read.items.sorted(Ordering.Tuple2(Ordering.Char, Ordering.Int))
The Ordering.Tuple2
здесь является общий метод, который создает экземпляр Ordering[(Char, Int)]
. Это то же самое, что и при определении нашего tupleOrdering
, но разница в том, что в случае val
это происходит один раз, тогда как в случае, когда он разрешен неявно, это происходит каждый раз, когда вызывается sorted
.
Таким образом, разница, которую вы видите, - это всего лишь стоимость создания экземпляра Decoder
в каждой операции, в отличие от его экземпляра за один раз в начале за пределами контрольного кода. Эта стоимость относительно крошечная, и для более крупных тестов это будет труднее увидеть.
Scala никогда не разрешает неявное во время выполнения – cchantep