【问题标题】:When is `private[this] def` a performance advantage over `private def`?`private[this] def` 何时比`private def` 具有性能优势?
【发布时间】:2016-08-19 09:16:29
【问题描述】:

与仅private def 相比,写private[this] def 在性能噪声比方面是否有意义?我知道private[this] valprivate val 不同,因为前者允许scalac 创建一个实际字段。但也许对于def 来说没有区别?最后,private[this] var呢?

有一个very similar question,但它不包含有关性能的具体陈述。

【问题讨论】:

    标签: performance scala visibility


    【解决方案1】:

    简答

    不,没有任何性能优势。 private defprivate[this] def 都被转换为字节码中的 privatepublic 方法,这取决于它们是否从不同的类中调用,而不取决于它们在 Scala 中的可见性。

    理论

    让我们从Scala language specificationprivate[this] 的评价开始:

    它只能从定义它的对象中访问。也就是说,选择 p.M 仅当前缀是 this 或 O.this 时才合法,对于包含引用的某些类 O。此外,适用于不合格私人的限制。

    您可以看到规范只是说明了语法上可接受与否的内容。 privateprivate[this] 都只能从同一类或内部类的实例中调用。在字节码中,您只能区分类级别的访问,而不是实例级别。因此,两个选项在字节码中应该是相同的,而 Scala 仅在编译期间强制执行差异。

    基本情况

    首先,我们来看一个简单的例子:

    class MyClass {
        private def privateDef(x: Int) = x
        private[this] def privateThisDef(x: Int) = x
    }
    

    这被翻译成字节码

    public class MyClass {
       private int privateDef(int);
       private int privateThisDef(int);
       public MyClass();
    }
    

    如您所见,两种方法都以private 结尾,因此从 JVM 的角度来看没有区别(例如,关于内联、静态/动态绑定等)。

    内部类

    当我们添加内部类时,这会发生什么变化?

    class MyClass {
      private def privateDef(x: Int) = x
      private[this] def privateThisDef(x: Int) = x
    
      class MyInnerClass{
        MyClass.this.privateDef(1)
        MyClass.this.privateThisDef(2)
      }
    }
    

    这被翻译成

    public class MyClass {
      public int MyClass$$privateDef(int);
      public int MyClass$$privateThisDef(int);
      public MyClass();
    }
    public class MyClass$MyInnerClass {
      public final MyClass $outer;
      public MyClass MyClass$MyInnerClass$$$outer();
      public MyClass$MyInnerClass(MyClass);
    }
    

    你可以看到这次MyClass中的两个方法都是public的,所以内部类可以调用它们。同样,privateprivate[this] 之间没有区别。

    同伴

    当可以从不同的类调用私有方法时,Scala 添加了一种特殊情况 - 当您从其各自类中的伴随对象调用私有方法时。 MyClass 的伴随对象将是一个单独的类,以字节码命名为 MyClass$。在同伴中调用private 方法会跨越类边界,因此此类方法将在字节码中公开。

    你不能在同伴之外调用private[this] 方法,但这只是一个语法限制。无论您可以在privateprivate[this] 之间进行选择,结果在字节码中都是相同的。

    变量

    vars 的行为似乎与 defs 的行为有些不同。本课

    class MyClass {
      private var privateVar = 0
      private[this] var privateThisVar = 0
      private var privateVarForInner = 0
      private[this] var privateThisForInner = 0
    
      class MyInnerClass{
        privateVarForInner = 1
        privateThisForInner = 1
      }
    }
    

    编译成

    public class MyClass {
      private int privateVar;
      private int privateThisVar;
      private int MyClass$$privateVarForInner;
      public int MyClass$$privateThisForInner;
      // ...
    }
    

    然后内部类对privateVar 使用setter,对privateThisVar 使用字段访问。我不确定为什么 scalac 会这样,我在规范中没有找到任何东西。也许这是特定于实现的东西。

    编辑: 根据要求,我创建了一个小型 JMH benchmark,比较了获取和设置 private varprivate[this] var 的性能。结果?根据JMH,所有操作都是≈ 10⁻⁸s。差异可以忽略不计,无论如何,内部类和vars 的情况很少见。

    【讨论】:

    • 同意,除了“与其他答案相反,没有任何性能优势”,我认为在其他答案中写得很清楚,没有性能优势。
    • 虽然这是对两者之间差异的一个很好的详细答案,并且可以从几乎相同的字节码中反映出这应该具有几乎相同的性能影响,但仍然缺乏性能测试来支持提出这个理论。
    • defs 的字节码相同,通过检查类文件进行验证。你真的认为测量两个相同字节码的性能值得吗?还是您的意思是测量代码不相同的var 的情况?
    • @Mifeet 我的意思是不同的地方。由于问题主要集中在性能的角度。当然,测试相同的字节码是没有意义的。
    • 好吧,唯一不完全相同的情况相当罕见且难以衡量,但请参阅 mu 更新的答案。差异低于 JMH 的分辨率。
    【解决方案2】:

    尝试编译和反编译(我使用 jd-gui)这段代码:

    class PrivateDefs {
      import PrivateDefs._
    
      private def privateMethod(x: Int) = privateMethodInObject(x)
    
      private[this] def privateThisMethod(x: Int) = privateMethodInObject(x)
    }
    
    object PrivateDefs {
    
      private[this] val N = 1000000000L
    
      def main(args: Array[String]) = {
        var i = 0
        var start = System.currentTimeMillis()
        while (i < N) {
          privateMethodInObject(1)
          i += 1
        }
        println("private method: " + (System.currentTimeMillis() - start) + " ms")
    
        i = 0
        start = System.currentTimeMillis()
        while (i < N) {
          privateThisMethodInObject(1)
          i += 1
        }
        println("private[this] method: " + (System.currentTimeMillis() - start) + " ms")
      }
    
      private def privateMethodInObject(x: Int) = x
    
      private[this] def privateThisMethodInObject(x: Int) = x
    
    }
    

    privateMethodInObjectprivateThisMethodInObject 的区别仅在访问修饰符(私有与公共)中的字节码中:

      public int org$test$PrivateDefs$$privateMethodInObject(int x)
      {
        return x;
      }
    
      private int privateThisMethodInObject(int x)
      {
        return x;
      }
    

    要比较性能,请运行代码大约 10 次。我在本地机器上进行 10 亿次方法调用大约需要 500 毫秒。当您运行几次测试时,数字几乎相同。所以我猜你可以找到除了性能之外的任何理由来选择一个选项而不是另一个选项。

    【讨论】:

    • 运行这样的性能测试是不好的。请参阅this question,了解如何使用微基准测试正确测量性能。 JMH 也是一个很好的起点。
    • 另外,这个答案是错误的。 privateMethodInObject 是公共的而privateThisMethodInObject 是私有的唯一原因是您只能在对象外部调用privateMethodInObject(即在class PrivateDefs 内部的方法中)。有关更多解释,请参阅我的答案。
    猜你喜欢
    • 1970-01-01
    • 2022-11-15
    • 2021-02-21
    • 1970-01-01
    • 2020-06-12
    • 1970-01-01
    • 1970-01-01
    • 2020-03-04
    • 1970-01-01
    相关资源
    最近更新 更多