Когда `private [this] def` выигрывает производительность перед` private def`?

Имеет ли смысл писать private[this] def с точки зрения соотношения "производительность-шум" по сравнению с просто private def? Я понимаю, что это имеет значение в отношении private[this] val over private val, потому что первое позволяет scalac создавать фактическое поле. Но, возможно, для def это не имеет значения? Наконец, как насчет private[this] var?

Существует очень похожий вопрос , но он не содержит конкретных утверждений о производительности.

Ответ 1

Короткий ответ

Нет, нет никакого преимущества в производительности. Оба private def и private[this] def переводятся в методы private или public в байт-коде в зависимости от того, вызывается ли их из другого класса, не зависит от того, какова их видимость в Scala.

Теория

Начнем с того, что Scala спецификация языка говорит о private[this]:

к нему можно получить доступ только из объекта, в котором он определен. То есть выбор p.M является только законным, если префикс - это или O.это, для некоторого класса O, охватывающего ссылку. Кроме того, применяются ограничения для неквалифицированных частных лиц.

Вы можете видеть, что в спецификации просто сказано, что является синтаксически приемлемым или нет. Оба private и private[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 (например, в отношении inlining, статической/динамической привязки и т.д.).

Внутренние классы

Как это изменяется при добавлении внутренних классов?

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 являются общедоступными, поэтому внутренний класс может их вызвать. Опять же, нет разницы между private и private[this].

Спутники

Scala добавляет еще один частный случай, когда частный метод может быть вызван из другого класса - когда вы вызываете частный метод из сопутствующего объекта в соответствующем классе. Сопутствующим объектом для MyClass будет отдельный класс с именем MyClass$ в байт-коде. Вызов метода private в компаньоне пересекает границы классов, и поэтому такой метод будет общедоступным в байт-коде.

Вы не можете вызвать метод private[this] вне компаньона, но это просто синтаксическое ограничение. Где бы вы ни выбрали между private и private[this], результат будет таким же в байте-коде.

Варс

Поведение для варов кажется несколько иным, чем для 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;
  // ...
}

Затем внутренний класс использует setter для privateVar и доступ к полю для privateThisVar. Я не знаю, почему именно так выглядит скайлак, я не нашел ничего в спецификации. Возможно, это что-то конкретное для реализации.

Изменить: по запросу я создал небольшой тест JMH сравнивающий производительность получения и установки private var и private[this] var. Результат? Все операции ≈ 10⁻⁸ соответствуют JMH. Разница незначительна, и случай с внутренними классами и var редок в любом случае.

Ответ 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

}

Разница между privateMethodInObject и privateThisMethodInObject в байтовом коде только в модификаторе доступа (private vs public):

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

  private int privateThisMethodInObject(int x)
  {
    return x;
  }

Чтобы сравнить производительность, запустите код примерно 10 раз. Я получил около 500 мс за 1 миллиард каждого вызова метода на моей локальной машине. Числа довольно одинаковы, когда вы запускаете тест несколько раз. Поэтому, я думаю, вы можете найти любую причину, кроме производительности, чтобы выбрать один вариант над другим.