Какое влияние на производительность оказывают неявные преобразования типов в Scala? - PullRequest
32 голосов
/ 17 июня 2011

Есть ли в Scala значительное влияние ЦП или памяти на использование неявных преобразований типов для расширения функциональных возможностей класса по сравнению с другими возможными вариантами реализации?

Например, рассмотрим глупую функцию манипуляции со строками. Эта реализация использует конкатенацию строк:

object Funky {
  def main(args: Array[String]) {
    args foreach(arg => println("Funky " + arg))
  }
}

Эта реализация скрывает конкатенацию за методом-членом, используя неявное преобразование типов:

class FunkyString(str: String) {
  def funkify() = "Funky " + str
}

object ImplicitFunky {
  implicit def asFunkyString(str: String) = new FunkyString(str)

  def main(args: Array[String]) {
    args foreach(arg => println(arg.funkify()))
  }
}

Оба делают одно и то же:

scala> Funky.main(Array("Cold Medina", "Town", "Drummer"))        
Funky Cold Medina
Funky Town
Funky Drummer

scala> ImplicitFunky.main(Array("Cold Medina", "Town", "Drummer"))
Funky Cold Medina
Funky Town
Funky Drummer

Есть ли разница в производительности? Несколько конкретных соображений:

Имеет ли Scala встроенные неявные вызовы метода asFunkyString?

Создает ли Scala новый объект-оболочку FunkyString для каждого аргумента или оптимизирует выделение дополнительных объектов?

Предположим, что FunkyString имеет 3 различных метода (funkify1, funkify2 и funkify3), и тело foreach вызывает каждый из них по очереди:

println(arg.funkify1())
println(arg.funkify2())
println(arg.funkify3())

Будет ли Scala повторять преобразование 3 раза или оптимизировать избыточные преобразования и делать это только один раз для каждой итерации цикла?

Предположим, вместо этого я явно фиксирую преобразование в другую переменную, например так:

val fs = asFunkyString(arg)
println(fs.funkify1())
println(fs.funkify2())
println(fs.funkify3())

Это меняет ситуацию?

С практической точки зрения, является ли широкое использование неявных преобразований потенциальной проблемой производительности или оно обычно безвредно?

Ответы [ 2 ]

19 голосов
/ 17 июня 2011

Я пытался настроить микробенчмарк, используя превосходный Scala-Benchmark-Template .

Очень сложно написать значимый (не оптимизированный JIT) тест, который тестирует тольконеявные преобразования, поэтому мне пришлось добавить немного накладных расходов.

Вот код:

class FunkyBench extends SimpleScalaBenchmark {
  val N = 10000
  def timeDirect( reps: Int ) = repeat(reps) {
    var strs = List[String]()
    var s = "a"
    for( i <- 0 until N ) {
      s += "a"
      strs ::= "Funky " + s 
    }
    strs
  }
  def timeImplicit( reps: Int ) = repeat(reps) {
    import Funky._
    var strs = List[String]()
    var s = "a"
    for( i <- 0 until N ) {
      s += "a"
      strs ::= s.funkify
    }
    strs
  }
}

И вот результаты:

[info] benchmark  ms linear runtime
[info]    Direct 308 =============================
[info]  Implicit 309 ==============================

Мой вывод: В любом нетривиальном фрагменте кода влияние неявных преобразований (создание объектов) невозможно измерить.

РЕДАКТИРОВАТЬ: Я использовал scala 2.9.0 иJava 1.6.0_24 (в режиме сервера)

10 голосов
/ 17 июня 2011

JVM может оптимизировать выделение дополнительных объектов, если обнаружит, что это будет достойно.

Это важно, потому что, если вы просто встраиваете что-то, вы в конечном итоге получаете более крупные методы, которые могут вызвать проблемы с производительностью кеша или даже снизить вероятность того, что JVM применяет другие оптимизации.

...