Suma
Suma

Reputation: 34423

Array constructor optimization - boxing of Double

When profiling my application, I can see boxing of Double values done in the Array[Double] constructor, like this (copied from JMC / JFR):

Double Double.valueOf(double)   1472
   Double BoxesRunTime.boxToDouble(double)  1472
      Object WrappedArray$ofDouble.apply(int)   1468
         Object IndexedSeqLike$Elements.next()  1365
            void Iterator.foreach(Function1)    1365
               void Iterator.foreach$(Iterator, Function1)  1365
                  void AbstractIterator.foreach(Function1)  1365
                     Object Array$.apply(Seq, ClassTag) 1365

When I check the Array constructor code, there is the following comment (see Array.scala#L182-L192):

  // Subject to a compiler optimization in Cleanup.
  // Array(e0, ..., en) is translated to { val a = new Array(3); a(i) = ei; a }

Such translation sounds like a reasonable thing to do. Why am I still seeing the Iterator / boxToDouble in my callstacks? Is the comment somehow obsolete, or do I need some specific compiler settings for this optimization to be applied?

The code calling the array constructor is a class member initialization:

  var elements = Array[Double](
    1, 0, 0, 0,
    0, 1, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1
  )

I have tested this with Scala 2.12.12 and 2.13.3

The comment was added in a commit Optimize primitive Array(e1, ..., en) (in Scala 2.11.0-M1).

Upvotes: 3

Views: 132

Answers (1)

Suma
Suma

Reputation: 34423

It seems the optimization is quite fragile. Changing the types and literals involved causes the optimization to be done (or not).

var el = Array[Double](1)

var el = Array[Double](1.0)

Both produce the ugly code:

       0: aload_0
       1: invokespecial #27                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: getstatic     #33                 // Field scala/Array$.MODULE$:Lscala/Array$;
       8: getstatic     #38                 // Field scala/runtime/ScalaRunTime$.MODULE$:Lscala/runtime/ScalaRunTime$;
      11: iconst_1
      12: newarray       double
      14: dup
      15: iconst_0
      16: dconst_1
      17: dastore
      18: invokevirtual #42                 // Method scala/runtime/ScalaRunTime$.wrapDoubleArray:([D)Lscala/collection/immutable/ArraySeq;
      21: getstatic     #47                 // Field scala/reflect/ClassTag$.MODULE$:Lscala/reflect/ClassTag$;
      24: invokevirtual #51                 // Method scala/reflect/ClassTag$.Double:()Lscala/reflect/ManifestFactory$DoubleManifest;
      27: invokevirtual #55                 // Method scala/Array$.apply:(Lscala/collection/immutable/Seq;Lscala/reflect/ClassTag;)Ljava/lang/Object;
      30: checkcast     #56                 // class "[D"
      33: putfield      #18                 // Field el:[D
      36: return
}

While

var el = Array(1.0)

var el: Array[Double] = Array(1)

produced beautiful result:

       0: aload_0
       1: invokespecial #22                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_1
       6: newarray       double
       8: dup
       9: iconst_0
      10: dconst_1
      11: dastore
      12: putfield      #13                 // Field el:[D
      15: return

I have tried Dotty as well and it seems to apply the optimization for all variants.

Posted as scala/bug#12201.

Upvotes: 4

Related Questions