Есть ли какие-либо временные или пространственные издержки для классов case по сравнению с обычными классами в Scala?

#scala

#scala

Вопрос:

Есть ли вообще какие-либо накладные расходы при использовании классов case в Scala по сравнению с обычными классами? Использует ли он дополнительную память, делает больше при построении или делает больше при доступе к полю? Или это буквально просто free equals/hashcode/tostring/apply/ unapply / etc для классов в нижней части иерархии типов?

Мой вариант использования — это класс, который заслуживает того, чтобы быть классом case (неизменяемым и равным, если все поля равны), но я нахожусь в домене, где производительность имеет решающее значение.

(Пожалуйста, не отвечайте в духе «перестаньте беспокоиться о преждевременной оптимизации».)

Комментарии:

1. Это не ответ на ваш вопрос, но вы можете сами увидеть методы, которые Scala генерирует для вашего класса case, используя -print флаг для компилятора : scalac -print Whatever.scala .

Ответ №1:

Классы Case всегда сохраняют все свои параметры в виде полей. Другие классы работают, только если на их параметры ссылается какой-либо метод. Это единственное различие в производительности, о котором я могу думать, за исключением большего размера кода из-за дополнительных методов.

Комментарии:

1. Спасибо! Я заметил, что в выводе ‘scalac -print’ у него есть «scala. Product$class./*Product$class*/ $init $ (Case.this);» в конструкторе. Знаете ли вы, выполняет ли это дополнительную работу помимо того, что делает вызов суперконструктора?

2. Сохраняются ли также ссылки на параметры, которые вызываются только внутри конструктора (тела класса)?

Ответ №2:

Если вы скомпилируете минимальный пример:

 class Ordinary(val i: Int) { }

case class Case(i: Int) { }
  

вы обнаружите, что байт-код для обычного класса меньше (~ 700 против ~ 3500); вы также обнаружите, javap -c -private -v <Classname> что конструктор имеет дополнительный вызов метода для инициализатора признака продукта (который на самом деле ничего не делает, поэтому должен быть оптимизирован компилятором JIT).

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

Ответ №3:

Сначала очевидный: байт-код больше из-за созданных дополнительных методов.

Классы Case сохраняют параметры первого списка параметров в качестве val членов. Если вам не нужно обращаться к параметру, кроме как во время построения объекта, это расточительно.

Начиная с Scala 2.8, классы case также сохраняют параметры из последующих разделов параметров для поддержки copy метода. Это деталь реализации, и она может быть изменена.

 scala> case class A(a: Int)(b: Int)
defined class A

scala> val a = A(0)(1)
a: A = A(0)

scala> a.copy()()
res9: A = A(0)

scala> :javap -private A
Compiled from "<console>"
public class A extends java.lang.Object implements scala.ScalaObject,scala.Product,scala.Serializable{
    private final int a;
    private final int b;
    public scala.collection.Iterator productIterator();
    public scala.collection.Iterator productElements();
    public int a();
    public A copy(int, int);
    public int copy$default$2(int);
    public int copy$default$1();
    public int hashCode();
    public java.lang.String toString();
    public boolean equals(java.lang.Object);
    public java.lang.String productPrefix();
    public int productArity();
    public java.lang.Object productElement(int);
    public boolean canEqual(java.lang.Object);
    private final boolean gd1$1(int);
    public A(int, int);
}