Groovy / Scala / Java под капотом

Я использовал Java в течение подобных 6-7 лет, затем несколько месяцев назад я обнаружил Groovy и начал сохранять большой ввод.. затем я задался вопросом, как определенные вещи работали под капотом (потому что отличная производительность действительно плоха), и понял, что, чтобы дать Вам динамический контроль типов каждый объект Groovy является a MetaClass возразите, что обрабатывает все вещи, которые JVM не могла обработать отдельно. Конечно, это представляет слой в середине между тем, что Вы пишете и что Вы выполняете, который замедляет все.

Затем несколько somedays назад я начал получать некоторый infos о Scala. Как эти два языка выдерживают сравнение в их переводах кода байта? Насколько вещи они добавляют к нормальной структуре, что она была бы получена простым кодом Java?

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

Кто-то может просветить меня?

От WizardOfOdds комментируют, что кажется, что единственный способ получить меньше ввода и ту же производительность состоял бы в том, чтобы записать промежуточного переводчика, который переводит что-то в коде Java (разрешение javac компилируют его), не чередуясь, как вещи выполняются, просто добавив synctatic сахар withour заботящийся о других нейтрализациях самого языка.

10
задан 2 revs 27 March 2010 в 15:10
поделиться

6 ответов

Scala все более эффективно сокращает затраты на абстракцию.

В комментариях, встроенных в код, я объясняю характеристики производительности доступа к массивам, расширенных типов, структурных типов и абстрагирования над примитивами и объектами.

Массивы

object test {
  /**
   * From the perspective of the Scala Language, there isn't a distinction between
   * objects, primitives, and arrays. They are all unified under a single type system,
   * with Any as the top type.
   *
   * Array access, from a language perspective, looks like a.apply(0), or a.update(0, 1)
   * But this is compiled to efficient bytecode without method calls. 
   */
  def accessPrimitiveArray {
    val a = Array.fill[Int](2, 2)(1)
    a(0)(1) = a(1)(0)        
  }
  // 0: getstatic #62; //Field scala/Array$.MODULE$:Lscala/Array$;
  // 3: iconst_2
  // 4: iconst_2
  // 5: new #64; //class test$$anonfun$1
  // 8: dup
  // 9: invokespecial #65; //Method test$$anonfun$1."<init>":()V
  // 12:  getstatic #70; //Field scala/reflect/Manifest$.MODULE$:Lscala/reflect/Manifest$;
  // 15:  invokevirtual #74; //Method scala/reflect/Manifest$.Int:()Lscala/reflect/AnyValManifest;
  // 18:  invokevirtual #78; //Method scala/Array$.fill:(IILscala/Function0;Lscala/reflect/ClassManifest;)[Ljava/lang/Object;
  // 21:  checkcast #80; //class "[[I"
  // 24:  astore_1
  // 25:  aload_1
  // 26:  iconst_0
  // 27:  aaload
  // 28:  iconst_1
  // 29:  aload_1
  // 30:  iconst_1
  // 31:  aaload
  // 32:  iconst_0
  // 33:  iaload
  // 34:  iastore
  // 35:  return

Pimp My Library

  /**
   * Rather than dynamically adding methods to a meta-class, Scala
   * allows values to be implicity converted. The conversion is
   * fixed at compilation time. At runtime, there is an overhead to
   * instantiate RichAny before foo is called. HotSpot may be able to
   * eliminate this overhead, and future versions of Scala may do so
   * in the compiler.
   */
  def callPimpedMethod {    
    class RichAny(a: Any) {
      def foo = 0
    }
    implicit def ToRichAny(a: Any) = new RichAny(a)
    new {}.foo
  }
  // 0: aload_0
  //   1: new #85; //class test$$anon$1
  //   4: dup
  //   5: invokespecial #86; //Method test$$anon$1."<init>":()V
  //   8: invokespecial #90; //Method ToRichAny$1:(Ljava/lang/Object;)Ltest$RichAny$1;
  //   11:  invokevirtual #96; //Method test$RichAny$1.foo:()I
  //   14:  pop
  //   15:  return

Структурные типы (также известные как Duck Typing)

  /**
   * Scala allows 'Structural Types', which let you have a compiler-checked version
   * of 'Duck Typing'. In Scala 2.7, the invocation of .size was done with reflection.
   * In 2.8, the Method object is looked up on first invocation, and cached for later
   * invocations..
   */
  def duckType {
    val al = new java.util.ArrayList[AnyRef]
    (al: { def size(): Int }).size()
  }
  // [snip]
  // 13:  invokevirtual #106; //Method java/lang/Object.getClass:()Ljava/lang/Class;
  // 16:  invokestatic  #108; //Method reflMethod$Method1:(Ljava/lang/Class;)Ljava/lang/reflect/Method;
  // 19:  aload_2
  // 20:  iconst_0
  // 21:  anewarray #102; //class java/lang/Object
  // 24:  invokevirtual #114; //Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
  // 27:  astore_3
  // 28:  aload_3
  // 29:  checkcast #116; //class java/lang/Integer

Специализация

  /**
   * Scala 2.8 introduces annotation driven specialization of methods and classes. This avoids
   * boxing of primitives, at the cost of increased code size. It is planned to specialize some classes
   * in the standard library, notable Function1.
   *
   * The type parameter T in echoSpecialized is annotated to instruct the compiler to generated a specialized version
   * for T = Int.
   */
  def callEcho {    
    echo(1)
    echoSpecialized(1)
  }
  // public void callEcho();
  //   Code:
  //    Stack=2, Locals=1, Args_size=1
  //    0:   aload_0
  //    1:   iconst_1
  //    2:   invokestatic    #134; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
  //    5:   invokevirtual   #138; //Method echo:(Ljava/lang/Object;)Ljava/lang/Object;
  //    8:   pop
  //    9:   aload_0
  //    10:  iconst_1
  //    11:  invokevirtual   #142; //Method echoSpecialized$mIc$sp:(I)I
  //    14:  pop
  //    15:  return


  def echo[T](t: T): T = t
  def echoSpecialized[@specialized("Int") T](t: T): T = t
}

Замыкания и понимание

В Scala для транслируется в цепочку вызовов в функции высшего порядка: foreach , map , flatMap и withFilter . Это действительно мощно, но вы должны знать, что следующий код не так эффективен, как похожая конструкция в Java. Scala 2.8 будет @specialize Function1 как минимум для Double и Int , и, надеюсь, также будет @specialize Traversable # foreach , что, по крайней мере, снизит стоимость упаковки.

Тело for-complation передается как закрытие, которое компилируется в анонимный внутренний класс.

def simpleForLoop {
  var x = 0
  for (i <- 0 until 10) x + i
}
// public final int apply(int);   
// 0:   aload_0
// 1:   getfield    #18; //Field x$1:Lscala/runtime/IntRef;
// 4:   getfield    #24; //Field scala/runtime/IntRef.elem:I
// 7:   iload_1
// 8:   iadd
// 9:   ireturn


// public final java.lang.Object apply(java.lang.Object);

// 0:   aload_0
// 1:   aload_1
// 2:   invokestatic    #35; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
// 5:   invokevirtual   #37; //Method apply:(I)I
// 8:   invokestatic    #41; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
// 11:  areturn

// public test$$anonfun$simpleForLoop$1(scala.runtime.IntRef);
// 0:   aload_0
// 1:   aload_1
// 2:   putfield    #18; //Field x$1:Lscala/runtime/IntRef;
// 5:   aload_0
// 6:   invokespecial   #49; //Method scala/runtime/AbstractFunction1."<init>":()V
// 9:   return

LineNumberTable: строка 4: 0

// 0:   new #16; //class scala/runtime/IntRef
// 3:   dup
// 4:   iconst_0
// 5:   invokespecial   #20; //Method scala/runtime/IntRef."<init>":(I)V
// 8:   astore_1
// 9:   getstatic   #25; //Field scala/Predef$.MODULE$:Lscala/Predef$;
// 12:  iconst_0
// 13:  invokevirtual   #29; //Method scala/Predef$.intWrapper:(I)Lscala/runtime/RichInt;
// 16:  ldc #30; //int 10
// 18:  invokevirtual   #36; //Method scala/runtime/RichInt.until:(I)Lscala/collection/immutable/Range$ByOne;
// 21:  new #38; //class test$$anonfun$simpleForLoop$1
// 24:  dup
// 25:  aload_1
// 26:  invokespecial   #41; //Method test$$anonfun$simpleForLoop$1."<init>":(Lscala/runtime/IntRef;)V
// 29:  invokeinterface #47,  2; //InterfaceMethod scala/collection/immutable/Range$ByOne.foreach:(Lscala/Function1;)V
// 34:  return
23
ответ дан 3 December 2019 в 13:19
поделиться

Множество хороших ответов, я постараюсь добавить что-нибудь еще, что я получил из вашего вопроса. нет обертывания объектов Scala. Например, следующие два класса в Scala и Java соответственно генерируют один и тот же байт-код:

// This is Scala
class Counter {
  private var x = 0
  def getCount() = {
    val y = x
    x += 1
    y
  }
}

// This is Java
class Counter {
  private int x = 0;

  private int x() {
    return x;
  }

  private void x_$eq(int x) {
    this.x = x;
  }

  public int getCounter() {
    int y = x();
    x_$eq(x() + 1);
    return y;
  }
}

Особого внимания заслуживает тот факт, что Scala всегда обращается к полям через геттеры и сеттеры, даже в других методах того же класса. Дело, однако, в том, что здесь не происходит никакого обертывания классов. То же самое, скомпилировано ли оно на Java или Scala.

Теперь Scala упрощает написание более медленного кода. Вот некоторые примеры:

  • Scala для заметно медленнее, чем Java при увеличении индексов - пока решение состоит в использовании вместо них циклов while , хотя кто-то написал плагин компилятора, который выполняет это преобразование автоматически. Рано или поздно такая оптимизация будет добавлена.

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

  • Также легко параметризовать функции, чтобы можно было передавать Int , что может привести к снижению производительности при работе с примитивами (в Scala, подклассы AnyVal ).

Вот пример класса, написанного на Scala двумя разными способами, где более компактный примерно в два раза медленнее:

class Hamming extends Iterator[BigInt] {
  import scala.collection.mutable.Queue
  val qs = Seq.fill(3)(new Queue[BigInt])
  def enqueue(n: BigInt) = qs zip Seq(2, 3, 5) foreach { case (q, m) => q enqueue n * m }
  def next = {
    val n = qs map (_.head) min;
    qs foreach { q => if (q.head == n) q.dequeue }
    enqueue(n)
    n
  }
  def hasNext = true
  qs foreach (_ enqueue 1)
}

class Hamming extends Iterator[BigInt] {
  import scala.collection.mutable.Queue
  val q2 = new Queue[BigInt]
  val q3 = new Queue[BigInt]
  val q5 = new Queue[BigInt]
  def enqueue(n: BigInt) = {
    q2 enqueue n * 2
    q3 enqueue n * 3
    q5 enqueue n * 5
  }
  def next = {
    val n = q2.head min q3.head min q5.head
    if (q2.head == n) q2.dequeue
    if (q3.head == n) q3.dequeue
    if (q5.head == n) q5.dequeue
    enqueue(n)
    n
  }
  def hasNext = true
  List(q2, q3, q5) foreach (_ enqueue 1)
}

Это также хороший пример того, как можно идеально сбалансировать производительность, когда это необходимо. . Более быстрая версия использует foreach в конструкторе, например, где это не вызовет проблем с производительностью.

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

10
ответ дан 3 December 2019 в 13:19
поделиться

Вы можете транслитерировать Java в Scala и получить почти такой же байт-код. Итак, Scala вполне может быть такой же быстрой, как Java.

Тем не менее, существует множество способов написать более медленный, более интенсивный в памяти код Scala, который короче и удобнее для чтения, чем его эквивалент на Java. И это хорошо! Мы используем Java, а не C, потому что защита памяти улучшает наш код. Дополнительная выразительность Scala означает, что вы можете писать программы, которые короче, чем, следовательно, менее глючны, чем в Java. Иногда это сказывается на производительности, но в большинстве случаев это не так.

6
ответ дан 3 December 2019 в 13:19
поделиться

ретроним, а у Дэвида есть рассмотрены основные моменты, касающиеся Scala: он по сути так же быстр, как Java, и это так, потому что он статически типизирован (таким образом, не требует дополнительных проверок во время выполнения) и использует легкие оболочки, которые JVM обычно может полностью удалить.

Scala действительно упрощает использование мощных универсальных библиотечных функций. Как и с любой мощной универсальной библиотекой в ​​Java, с ней связано некоторое снижение производительности. Например, использование java.util.HashMap для реализации сопоставления байтов и байтов будет мучительно медленным в Java (по сравнению с примитивной таблицей поиска в массиве), и в Scala оно будет столь же медленным. Но Scala предоставляет вам гораздо больше возможностей такого рода и делает их удивительно простым вызовом, до такой степени, что вы действительно можете попросить о невероятном объеме работы, выполняемой с помощью очень небольшого количества кода. Как всегда, когда вы упрощаете задачу, чтобы просить о многом, люди иногда просят о многом, а затем задаются вопросом, почему это занимает так много времени. (И легкость вопроса делает это тем более удивительным, когда человек узнает (или тщательно обдумывает), что должно происходить за кулисами.)

Единственная законная критика, которую можно вызвать, это то, что Scala не делает это так просто, как она возможно мог написать высокопроизводительный код; большинство функций, упрощающих использование, направлены на универсальное функциональное программирование, которое по-прежнему выполняется довольно быстро, но не так быстро, как прямой доступ к примитивным типам.Например, в Scala есть невероятно мощный цикл for , но он использует универсальные типы, поэтому примитивы должны быть упакованы, и, следовательно, вы не можете эффективно использовать его для итерации по примитивным массивам; вместо этого вы должны использовать цикл while . (Разница в производительности, вероятно, уменьшится в версии 2.8 при использовании упомянутых ретронимов специализаций.)

6
ответ дан 3 December 2019 в 13:19
поделиться

Одна вещь, о которой следует знать: Java 7 представит новый invokedynamic байткод для JVM, который сделает ненужной большую часть "магии метаклассов" Groovy и должен значительно ускорить реализацию динамических языков на JVM.

9
ответ дан 3 December 2019 в 13:19
поделиться

Другие ответы посвящены специфике scala. Я хотел бы добавить несколько моментов для общего случая. Во-первых, вполне реально написать генератор байт-кода, который генерирует javac-подобный код, но на языке, отличном от java. Это становится сложнее, поскольку семантика языка отличается от семантики Java. Однако явная типизация не является частью семантики, только синтаксиса (и имеет свойства обнаружения ошибок).

Производительность снижается в том случае, если типы не могут быть определены статически (во время компиляции), или если язык является динамическим по своей природе (типизация динамическая, как во многих языках сценариев, таких как javascript, jython, jruby и т. Д.). В тех случаях с 1.6 jdk вам нужно выполнить диспетчеризацию на основе отражения. Это, очевидно, медленнее и не может быть легко оптимизировано с помощью точки доступа / виртуальной машины. Jdk 1.7 расширяет invokedynamic, чтобы его можно было использовать для динамического вызова функции, поддерживаемой языками сценариев.

Компилятор javac не выполняет столько оптимизаций (jvm выполняет их во время выполнения), поэтому язык java довольно просто отображает байт-код java. Это означает, что языки с одинаковой семантикой имеют преимущество по сравнению с языками с другой семантикой. Это недостаток JVM и место, где CLR (среда выполнения .net) и LLVM имеют явные преимущества.

1
ответ дан 3 December 2019 в 13:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: