Как.min Scala избежать наказания за бокс и распаковку?
Vector.min
реализуется как
def min[B >: A](implicit cmp: Ordering[B]): A = {
if (isEmpty)
throw new UnsupportedOperationException("empty.min")
reduceLeft((x, y) => if (cmp.lteq(x, y)) x else y)
}
и когда вы профиль
Vector.fill(1000000)(scala.util.Random.nextLong).min
это быстро, и нет никакого бокса или распаковки. Если, однако, вы пишете явно эквивалентный
val cmp = implicitly[Ordering[Long]]
Vector.fill(1000000)(scala.util.Random.nextLong).reduceLeft((x, y) => if (cmp.lteq(x, y)) x else y)
он работает примерно в 10 раз медленнее (игнорируя время в Random, которое в противном случае доминирует в этом, и да, я подогрел свои тесты...).
Как первая версия избегает снижения производительности бокса?
Изменить: вот мой код профилирования:
val cmp = implicitly[Ordering[Long]]
def randomLongs = Vector.fill(1000000)(scala.util.Random.nextLong)
def timing[R](f: => R): (Long, R) = {
val startTime = System.nanoTime
val result = f
((System.nanoTime - startTime) / 1000000, result)
}
def minTiming = { val r = randomLongs; timing(r.min)._1 }
def reduceLeftTiming = { val r = randomLongs; timing(r.reduceLeft((x, y) => if (cmp.lteq(x, y)) x else y))._1 }
while(true) {
println((minTiming, reduceLeftTiming))
}
и я вижу раз, используя min
около 20 мс, используя reduceLeft
~200 мс. Я профилировал этот код, используя YourKit
; вот скриншот дерева вызовов, показывающий, что min
не приводит ни к какому боксу.
1 ответ
Я думаю, что первая версия выводит java.lang.Long
за B
, Таким образом, бокс все еще продолжается, но только при заполнении вектора, и после этого все сравнения выполняются между помещенными в коробку объектами.
Во второй версии, так как тип cmp
дается как Ordering[Long]
, java.lang.Long
s в векторе должны быть распакованы перед передачей в cmp.lteq(x, y)
,