Действительно ли абстрактные классы в Scala лучше, чем черты? - PullRequest
13 голосов
/ 10 февраля 2011

Выдержка из книги о лестнице:

Если эффективность очень важна, склоняйтесь к использованию класса.Большинство сред выполнения Java делают вызов виртуального метода членом класса более быстрой операцией, чем вызов метода интерфейса.Черты компилируются в интерфейсы и, следовательно, могут немного снизить производительность.Однако вам следует сделать этот выбор, только если вы знаете, что рассматриваемая черта представляет собой узкое место в производительности, и у вас есть доказательства того, что использование класса вместо этого фактически решает проблему.действительно происходит за кулисами.И я заметил, что invokevirtual используется в случае абстрактного класса и invokeinterface в случае интерфейса.Но независимо от того, какой код я написал, они всегда выполнялись примерно одинаково.Я использую HotSpot 1.6.0_18 в режиме сервера.

Это JIT делает такую ​​большую работу по оптимизации?У кого-нибудь есть пример кода, который доказывает утверждение из книги о том, что invokevirutal - более быстрая операция?

Ответы [ 3 ]

7 голосов
/ 11 февраля 2011

Если HotSpot замечает, что все экземпляры на сайте вызова имеют одинаковый тип, он может использовать мономорфный вызов метода, и как виртуальные, так и интерфейсные методы оптимизируются одинаково. В документах PerformanceTechniques и VirtualCalls не проводится различий между виртуальными и интерфейсными методами.

Но в общем немономорфном случае может быть какая-то разница. Документ InterfaceCalls гласит:

Не существует простой схемы префиксов, в которой методы интерфейса отображаются с фиксированным смещением в каждом классе, который реализует этот интерфейс. Вместо этого в общем (немономорфном) случае подпрограмма с кодировкой ассемблера должна извлечь список реализованных интерфейсов из klassOop получателя и просмотреть этот список в поисках текущего целевого интерфейса.

Это также подтверждает, что мономорфный случай одинаков для обоих:

Практически те же оптимизации применяются к интерфейсным вызовам, что и к виртуальным вызовам. Как и в случае виртуальных вызовов, большинство интерфейсных вызовов являются мономорфными и поэтому могут быть обработаны как прямые вызовы с дешевой проверкой.

Другие JVM могут иметь разные оптимизации.

Вы можете попробовать микро-тест (, если вы знаете, как ), который вызывает методы для нескольких классов, реализующих один и тот же интерфейс, и для нескольких классов, расширяющих один и тот же абстрактный класс. Таким образом, должна быть возможность заставить JVM использовать немономорфные вызовы методов. (Хотя в реальной жизни любое различие может не иметь значения, поскольку большинство сайтов вызовов в любом случае мономорфны.)

3 голосов
/ 11 февраля 2011

Суть в том, что вам придется измерить его самостоятельно для собственного приложения, чтобы увидеть, важно ли это.Вы можете получить довольно противоречивые результаты с текущей JVM.Попробуйте это.

Файл TraitAbstractPackage.scala

package traitvsabstract

trait T1 { def x: Int; def inc: Unit }
trait T2 extends T1 { def x_=(x0: Int): Unit }
trait T3 extends T2 { def inc { x = x + 1 } }

abstract class C1 { def x: Int; def inc: Unit }
abstract class C2 extends C1 { def x_=(x0: Int): Unit }
abstract class C3 extends C2 { def inc { x = x + 1 } }

Файл TraitVsAbstract.scala

object TraitVsAbstract {
  import traitvsabstract._

  class Ta extends T3 { var x: Int = 0}
  class Tb extends T3 {
    private[this] var y: Long = 0
    def x = y.toInt
    def x_=(x0: Int) { y = x0 } 
  }
  class Tc extends T3 {
    private[this] var xHidden: Int = 0
    def x = xHidden
    def x_=(x0: Int) { if (x0 > xHidden) xHidden = x0 }
  }

  class Ca extends C3 { var x: Int = 0 }
  class Cb extends C3 {
    private[this] var y: Long = 0
    def x = y.toInt
    def x_=(x0: Int) { y = x0 } 
  }
  class Cc extends C3 {
    private[this] var xHidden: Int = 0
    def x = xHidden
    def x_=(x0: Int) { if (x0 > xHidden) xHidden = x0 }
  }

  def Tbillion3(t: T3) = {
    var i=0; while (i<1000000000) { t.inc; i+=1 }; t.x
  }

  def Tbillion1(t: T1) = {
    var i=0; while (i<1000000000) { t.inc; i+=1 }; t.x
  }

  def Cbillion3(c: C3) = {
    var i=0; while (i<1000000000) { c.inc; i+=1 }; c.x
  }

  def Cbillion1(c: C1) = {
    var i=0; while (i<1000000000) { c.inc; i+=1 }; c.x
  }

  def ptime(f: => Int) {
    val t0 = System.nanoTime
    val ans = f.toString
    val t1 = System.nanoTime
    printf("Answer: %s; elapsed: %.2f seconds\n",ans,(t1-t0)*1e-9)
  }

  def main(args: Array[String]) {
    for (i <- 1 to 3) {
      println("Iteration "+i)
      val t1s,t3s = List(new Ta, new Tb, new Tc)
      val c1s,c3s = List(new Ca, new Cb, new Cc)
      t1s.foreach(x => ptime(Tbillion1(x)))
      t3s.foreach(x => ptime(Tbillion3(x)))
      c1s.foreach(x => ptime(Cbillion1(x)))
      c3s.foreach(x => ptime(Cbillion3(x)))
      println
    }
  }
}

Каждый должен распечатать 1000000000 в качестве ответа, а также времядолжно быть равно нулю (если JVM действительно умная) или примерно столько же, сколько нужно, чтобы добавить миллиард чисел.Но, по крайней мере, в моей системе Sun JVM оптимизирует в обратном направлении - повторные прогоны становятся медленнее - а абстрактные классы медленнее, чем черты.(Возможно, вы захотите запустить с java -XX:+PrintCompilation, чтобы попытаться выяснить, что идет не так; я подозреваю, что зомби.)

Кроме того, стоит отметить, что scalac -optimise ничего не делает для улучшения ситуации - все зависит отJVM.

В отличие от JVM JRockit, он демонстрирует стабильную среднюю производительность, но, опять же, черты побеждают классы.Поскольку время согласовано, я сообщу о них: 3,35 с для классов (3,62 с для класса с оператором if) против 2,51 секунды для всех признаков, оператора if или нет.

(Iсчитаю, что эта тенденция в целом верна: Hotspot в некоторых случаях демонстрирует невероятно высокую производительность, а в других (например, в этом случае) запутывается и ужасно медленен; JRockit никогда не бывает супербыстрым - не пытайтесь получить C-подобныйпроизводительность даже из примитивов - но это редко приводит к ошибкам.)

0 голосов
/ 11 февраля 2011

Цитата из Внутри виртуальной машины Java (Инструкции по вызову и скорость) :

Когда виртуальная машина Javaвстречает инструкцию invokevirtual и разрешает символьную ссылку на прямую ссылку на метод экземпляра, эта прямая ссылка, вероятно, является смещением в таблице методов.С этого момента, то же самое смещение может быть использовано.Однако для инструкции invokeinterface виртуальная машина должна будет выполнять поиск в таблице методов каждый раз, когда встречается инструкция, поскольку она не может предположить, что смещение совпадает с предыдущим временем.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...