Как Scala.min избежать штрафа за бокс и распаковку?

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 в векторе должен быть распакован, прежде чем передать его на cmp.lteq(x, y).