如何识别反编译的 Scala 代码中的装箱/拆箱?

发布于 2024-10-17 07:19:45 字数 275 浏览 2 评论 0原文

在对这个问题的公认最佳回答中,有一个明确的解释为什么会发生拳击。

但是,如果我反编译代码(使用 java 反编译器),我看不到 scala.runtime.BoxesRunTime 的使用。此外,如果我分析代码(使用 JProfiler),我看不到 BoxesRunTime 的任何实例。

那么,我如何真正看到装箱/拆箱发生的证据呢?

In the accepted best response to this question, there is a clear explanation why boxing happens.

However, if I decompile the code (using java decompiler) I cannot see use of scala.runtime.BoxesRunTime. Furthermore, if I profile the code (using JProfiler) I cannot see any instances of BoxesRunTime.

So, how do I really see a proof of boxing/unboxing taking place?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

情绪少女 2024-10-24 07:19:45

在此代码中:

class Foo[T] {
  def bar(i: T) = i
}


object Main {
  def main(args: Array[String]) {
    val f = new Foo[Int]
    f.bar(5)
  }
}

调用 bar 应首先对整数进行装箱。使用 Scala 2.8.1 进行编译并使用:

javap -c -l -private -verbose -classpath <dir> Main$

查看为 Main 类的 main 方法生成的字节码:

public void main(java.lang.String[]);                                                      
...                                                                                   
   9:   iconst_5                                                                                          
   10:  invokestatic    #24; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;      
   13:  invokevirtual   #28; //Method Foo.bar:(Ljava/lang/Object;)Ljava/lang/Object;        
   16:  pop                                                                                            
   17:  return                                                                                     
...

您可以看到对 BoxesRunTime 在调用 bar 之前。

BoxesRunTime 是一个包含原始类型装箱方法的对象,因此总共应该有一个实例。这里的技巧是库中的这个特定文件是用 Java 编写的,并且转换是静态方法。因此,尽管在 Scala 代码中使用它感觉就像是一个对象,但在运行时没有任何它的实例。

您可能应该使用 JProfile 查找装箱原语(例如 java.lang.Integer),尽管我不确定 JVM 是如何工作的以及它是否实际上可以在运行时重写代码并对其进行优化以避免装箱。据我所知,它不应该应用专业化(但我相信 CLR 可以)。有或没有装箱情况的一些微基准测试是了解运行时发生情况的另一种方法。

编辑:

上面假设类型参数未使用 @specialized 注释进行注释。在这种情况下,可以避免装箱/拆箱。标准库中的某些类是专门的。请参阅此 sid

In this code:

class Foo[T] {
  def bar(i: T) = i
}


object Main {
  def main(args: Array[String]) {
    val f = new Foo[Int]
    f.bar(5)
  }
}

The invocation of bar should first box the integer. Compiling with Scala 2.8.1 and using:

javap -c -l -private -verbose -classpath <dir> Main$

to see the bytecode produced for the main method of the Main class yields:

public void main(java.lang.String[]);                                                      
...                                                                                   
   9:   iconst_5                                                                                          
   10:  invokestatic    #24; //Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;      
   13:  invokevirtual   #28; //Method Foo.bar:(Ljava/lang/Object;)Ljava/lang/Object;        
   16:  pop                                                                                            
   17:  return                                                                                     
...

You can see the call to BoxesRunTime before the call to bar.

BoxesRunTime is an object which contains boxing methods for primitive types, so there should be exactly one instance in total. The trick here is that this particular file in the library was written in Java, and the conversions are static methods. For this reason there aren't any instances of it at runtime, although using it in Scala code feels as if it were an object.

You should probably look for boxed primitives (e.g. java.lang.Integer) with JProfile, though I am uncertain how the JVM works and whether it may actually rewrite the code at runtime and optimize it to avoid boxing. To my knowledge, it shouldn't apply specialization (but I believe CLR does). A few microbenchmarks with and without the boxing situation are another way to figure out what happens at runtime.

EDIT:

The above is assuming that a type parameter wasn't annotated with the @specialized annotation. In this case, the boxing/unboxing can be avoided. Certain classes in the standard library are specialized. See this sid.

强者自强 2024-10-24 07:19:45

给定以下 Test.scala 程序:

object Test {
  def main(args:Array[String]) {
    val list = List(1,5,15)
    val res = list.map(e => e*2).filter(e => e>10)
  }
}

如果我使用 scalac -Xprint:jvm Test.scala 进行编译,我会得到此片段,表明发生了专门化(抱歉粘贴得很宽):

package <empty> {
  final class Test extends java.lang.Object with ScalaObject {
    def main(args: Array[java.lang.String]): Unit = {
      val list: List = immutable.this.List.apply(scala.this.Predef.wrapIntArray(Array[Int]{1, 5, 15}));
      val res: List = list.map({
        (new Test$anonfun$1(): Function1)
      }, immutable.this.List.canBuildFrom()).$asInstanceOf[scala.collection.TraversableLike]().filter({
        (new Test$anonfun$2(): Function1)
      }).$asInstanceOf[List]();
      ()
    };
    def this(): object Test = {
      Test.super.this();
      ()
    }
  };
  @SerialVersionUID(0) @serializable final <synthetic> class Test$anonfun$1 extends scala.runtime.AbstractFunction1$mcII$sp {
    final def apply(e: Int): Int = Test$anonfun$1.this.apply$mcII$sp(e);
    <specialized> def apply$mcII$sp(v1: Int): Int = v1.*(2);
    final <bridge> def apply(v1: java.lang.Object): java.lang.Object = scala.Int.box(Test$anonfun$1.this.apply(scala.Int.unbox(v1)));
    def this(): Test$anonfun$1 = {
      Test$anonfun$1.super.this();
      ()
    }
  };
  @SerialVersionUID(0) @serializable final <synthetic> class Test$anonfun$2 extends scala.runtime.AbstractFunction1$mcZI$sp {
    final def apply(e: Int): Boolean = Test$anonfun$2.this.apply$mcZI$sp(e);
    <specialized> def apply$mcZI$sp(v1: Int): Boolean = v1.>(10);
    final <bridge> def apply(v1: java.lang.Object): java.lang.Object = scala.Boolean.box(Test$anonfun$2.this.apply(scala.Int.unbox(v1)));
    def this(): Test$anonfun$2 = {
      Test$anonfun$2.super.this();
      ()
    }
  }
}

可能就是您不这样做的原因查看字节码中任何装箱的证据...

Given the following Test.scala program:

object Test {
  def main(args:Array[String]) {
    val list = List(1,5,15)
    val res = list.map(e => e*2).filter(e => e>10)
  }
}

If I compile with scalac -Xprint:jvm Test.scala, I get this snippet suggesting that specialization occurs (sorry for wide paste):

package <empty> {
  final class Test extends java.lang.Object with ScalaObject {
    def main(args: Array[java.lang.String]): Unit = {
      val list: List = immutable.this.List.apply(scala.this.Predef.wrapIntArray(Array[Int]{1, 5, 15}));
      val res: List = list.map({
        (new Test$anonfun$1(): Function1)
      }, immutable.this.List.canBuildFrom()).$asInstanceOf[scala.collection.TraversableLike]().filter({
        (new Test$anonfun$2(): Function1)
      }).$asInstanceOf[List]();
      ()
    };
    def this(): object Test = {
      Test.super.this();
      ()
    }
  };
  @SerialVersionUID(0) @serializable final <synthetic> class Test$anonfun$1 extends scala.runtime.AbstractFunction1$mcII$sp {
    final def apply(e: Int): Int = Test$anonfun$1.this.apply$mcII$sp(e);
    <specialized> def apply$mcII$sp(v1: Int): Int = v1.*(2);
    final <bridge> def apply(v1: java.lang.Object): java.lang.Object = scala.Int.box(Test$anonfun$1.this.apply(scala.Int.unbox(v1)));
    def this(): Test$anonfun$1 = {
      Test$anonfun$1.super.this();
      ()
    }
  };
  @SerialVersionUID(0) @serializable final <synthetic> class Test$anonfun$2 extends scala.runtime.AbstractFunction1$mcZI$sp {
    final def apply(e: Int): Boolean = Test$anonfun$2.this.apply$mcZI$sp(e);
    <specialized> def apply$mcZI$sp(v1: Int): Boolean = v1.>(10);
    final <bridge> def apply(v1: java.lang.Object): java.lang.Object = scala.Boolean.box(Test$anonfun$2.this.apply(scala.Int.unbox(v1)));
    def this(): Test$anonfun$2 = {
      Test$anonfun$2.super.this();
      ()
    }
  }
}

Could be why you don't see any evidence of boxing in bytecode...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文