深入了解Groovy、Scala和Java [已结束]

iyr7buue  于 2022-12-03  发布在  Scala
关注(0)|答案(6)|浏览(159)

已关闭。此问题需要更多focused。当前不接受答案。
**想要改进此问题吗?**更新问题,使其仅关注editing this post的一个问题。

2天前关闭。
Improve this question
我使用Java有6-7年了,但是几个月前,我发现了Groovy,并开始保存大量的输入......然后我想知道某些东西在引擎盖下是如何工作的(因为Groovy的性能实在太差了),而且我明白,为了给予 * 动态类型 *,每个Groovy对象都是一个MetaClass对象,它可以处理JVM无法处理的所有事情。t handle by self.当然,这在你写的和你执行的中间引入了一个层,它减慢了一切。
几天前,我开始了解Scala。这两种语言在字节码翻译方面如何比较?它们在普通结构中添加了多少内容,而普通Java代码可以获得这些内容?
我的意思是,Scalastatic typed,所以Java类的 Package 器应该更简单,因为在编译时会检查很多东西,但我不确定内部的内容有什么真实的的区别。(我不是在讨论Scala与其他类相比的功能方面,那是不同的事情)
谁能告诉我?
从SyntaxT 3rr 0 r的评论中可以看出,要想获得更少的输入和相同的性能,唯一的方法是编写一个中间翻译器,在不改变执行方式的情况下翻译Java代码中的内容(让 javac 编译它),只是添加语法糖,而不考虑语言本身的其他缺陷。

yhqotfr8

yhqotfr81#

Scala在降低抽象成本方面做得越来越好。
在代码的注解中,我解释了数组访问、pumped类型、结构类型以及对原语和对象进行抽象的性能特征。

阵列

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

皮条客我的图书馆

/**
   * 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

结构类型(aka 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中,for被转换为对高阶函数的调用链:foreachmapflatMapwithFilter。这真的很强大,但是您需要注意,下面的代码远不如Java中类似的结构高效。Scala 2.8将@specialize Function 1至少用于DoubleInt,并且希望也@specialize Traversable#foreach。这将至少消除装箱成本。
for-comprehension的主体作为闭包传递,闭包被编译为匿名内部类。

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

行号表格:第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
5w9g7ksd

5w9g7ksd2#

有很多好的答案,我会尝试补充一些我从你的问题中得到的东西。Scala对象 * 没有 * Package 。例如,下面两个类,分别在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总是通过getter和setter访问字段,即使是在同一个类的其他方法上也是如此。然而,关键是这里绝对没有类 Package 。无论是用Java还是Scala编译,都是一样的。
现在,Scala让编写速度较慢的代码变得 * 更容易 *。

  • Scala的for在递增索引时明显比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,它不会导致性能问题。
最后,这完全是一个观点的问题。在对象上调用方法比直接调用函数和过程慢,这是面向对象编程的一个主要缺点,但事实证明,大多数时候这并不是一个大问题。

polkgigr

polkgigr3#

需要注意的一点是:Java7将为JVM引入一个新的invokedynamic字节码,这将使Groovy的许多“元类魔法”变得不必要,并将大大加快JVM上的动态语言实现。

vaj7vani

vaj7vani4#

你可以把Java翻译成Scala,得到几乎完全一样的字节码,所以Scala完全有能力和Java一样快。
也就是说,有很多方法可以编写更慢、更占用内存的Scala代码,这些代码比Java代码更短、更可读。这很好!我们使用Java,而不是C,因为内存保护可以改进我们的代码。Scala额外的表达能力意味着你可以编写更短的程序,因此比Java更少的bug。有时这会损害性能,但大多数时候不会。

yqyhoc1h

yqyhoc1h5#

retronym和大卫已经介绍了Scala的要点:它本质上与Java一样快,之所以如此,是因为它是静态类型化的(因此不需要额外的运行时检查),并且使用JVM通常可以完全删除的轻量级 Package 器。
Scala确实使使用强大的泛型库特性变得非常容易。与Java中任何强大的泛型库一样,这也有一些性能损失。例如,在Java中使用java.util.HashMap实现字节和字节之间的Map将非常慢(与原始数组查找表相比),在Scala中也会同样慢。但是Scala提供了更多这类特性,并且使调用它们变得惊人地容易,到了这样的地步,你真的可以要求用很少的代码完成大量的工作。和往常一样,当你让要求很多变得容易时,人们有时会要求很多,然后想为什么要花这么长的时间。(当一个人了解(或仔细思考)幕后必须发生什么时,要求的容易使人更加惊讶。)
人们可能提出的唯一合理的批评是Scala并没有尽可能容易地编写高性能代码;大多数易用性特性都是针对泛型函数编程的,这仍然是相当快的,但不如直接访问原语类型快。例如,Scala有一个非常强大的for循环,但它使用的是泛型类型,因此原语必须被装箱,因此不能有效地使用它来迭代原语数组;您必须使用while循环来代替。(在Python 2.8中,性能差异可能会随着前面提到的反词专门化而降低。)

xhv8bpkk

xhv8bpkk6#

其他的答案集中在Scala的细节上。我想为一般情况添加一些要点。首先,编写一个生成类似 * javac * 的代码的字节码生成器是完全可行的,但是使用的语言不是Java。由于语言语义与Java的语义不同,这变得更加困难。但是显式类型不是语义的一部分,仅语法的(并且它具有错误检测属性)。
如果类型不能静态地(在编译时)确定,或者如果语言本质上是动态的(键入是动态的,就像在许多脚本语言中一样,如JavaScriptJythonJRuby在使用1.6 JDK的情况下,您需要执行一些基于反射的调度。这显然较慢,并且不能轻松地通过hotspot /虚拟机。JDK 1.7扩展了invokedynamic,因此它实际上可以用于以脚本语言支持的动态方式调用函数。

  • javac* 编译器并不做很多优化(JVM在运行时做),所以Java语言直接Map到Java字节码。这意味着具有相同语义的语言比具有不同语义的语言有优势。这是JVM的缺点,也是CLR(.NET运行时)和LLVM有明显优势的地方。

相关问题