Lambdas generate more bytecode in Scala 3 than in Scala 2.13

Hi,

Recently I was debugging some issues regarding class size and I’ve discovered that Scala 3 generates more code for lambdas than Scala 2.13

Code:

object Calculator {
  def add(a1: => Int, a2: => Int): Int = a1 + a2
}

object Main {
  def main(args: Array[String]): Unit = {
    println(Calculator.add({
      Calculator.add({
        2
      }, {
        Calculator.add({
          Calculator.add({
            1
          }, {
            2
          })
        }, {
          Calculator.add({
            Calculator.add({
              1
            }, {
              Calculator.add({
                2
              }, {
                Calculator.add({
                  Calculator.add({
                    1
                  }, {
                    2
                  })
                }, {
                  Calculator.add({
                    Calculator.add({
                      1
                    }, {
                      2
                    })
                  }, {
                    Calculator.add({
                      1
                    }, {
                      2
                    })
                  })
                })
              })
            })
          }, {
            Calculator.add({
              1
            }, {
              2
            })
          })
        })
      })
    }, {
      Calculator.add({
        Calculator.add({
          1
        }, {
          2
        })
      }, {
        Calculator.add({
          Calculator.add({
            Calculator.add({
              2
            }, {
              Calculator.add({
                Calculator.add({
                  1
                }, {
                  2
                })
              }, {
                Calculator.add({
                  Calculator.add({
                    1
                  }, {
                    2
                  })
                }, {
                  Calculator.add({
                    1
                  }, {
                    2
                  })
                })
              })
            })
          }, {
            2
          })
        }, {
          Calculator.add({
            Calculator.add({
              2
            }, {
              Calculator.add({
                Calculator.add({
                  1
                }, {
                  2
                })
              }, {
                Calculator.add({
                  Calculator.add({
                    1
                  }, {
                    2
                  })
                }, {
                  Calculator.add({
                    1
                  }, {
                    2
                  })
                })
              })
            })
          }, {
            Calculator.add({
              2
            }, {
              Calculator.add({
                Calculator.add({
                  1
                }, {
                  2
                })
              }, {
                Calculator.add({
                  Calculator.add({
                    1
                  }, {
                    2
                  })
                }, {
                  Calculator.add({
                    1
                  }, {
                    2
                  })
                })
              })
            })
          })
        })
      })
    }))
  }
}

Scala 2.13:

Classfile target/scala-2.13/classes/Main$.class
  Last modified 26 maj 2024; size 9420 bytes

Scala 3.3:

Classfile target/scala-3.3.3/classes/Main$.class
  Last modified 26 maj 2024; size 14658 bytes

Scala 3.4:

Classfile target/scala-3.4.2/classes/Main$.class
  Last modified 26 maj 2024; size 13283 bytes

Was this size increase caused by some performance optimizations or is this a bug?

maybe it’s the difference between scala 2.13 pickling vs scala 3 tasty format? An Overview of TASTy | Scala Documentation

I don’t think so - these are Java classes, not intermediate representation.

afaik, scala compiler embeds the pickling and tasty metadata into *.class files. you can probably run javap in verbose mode to estimate size of any extra metadata embedded in *.class file.

I think maybe some optimizations are not been ported to Scala 3.

It looks like name mangling takes a hit:

  #348 = Utf8               main$$anonfun$2$$anonfun$2$$anonfun$2$$anonfun$2$$anonfun$2$$anonfun$1$$anonfun$1

compare to Scala 2

  #378 = Utf8               $anonfun$main$62

to prove myself :slight_smile: (i.e. that the metadata is in fact embedded) i’ve run javap -v on Main.class produced by scala 2.13 and one of the sections was:

SourceFile: "Main.scala"
RuntimeVisibleAnnotations:
  0: #6(#7=s#8)
    scala.reflect.ScalaSignature(
      bytes="\u0006\u0005\tUs!B\u001a5\u0011\u0.... very long string containing pickled metadata

You must check “Main$”, not Main. It also has metadata, but not so much.

Crudely,

javap -private -verbose '/tmp/sandbox/Main$.class' | grep Utf8

is 5630 for Scala 2 and 9615 for Scala 3.
or 3231 vs 6996 just for anonfun names.

For 70 lambdas, an extra 80 chars each would be 5K. (I did not measure them.)

The code is otherwise the same.

1 Like

Code is a bit different:

  68: #46 REF_invokeStatic java/lang/invoke/LambdaMetafactory.altMetafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #34 ()I
      #408 REF_invokeStatic Main$.$anonfun$main$31:()I
      #34 ()I
      #39 1
  68: #59 REF_invokeStatic java/lang/invoke/LambdaMetafactory.altMetafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #44 ()Ljava/lang/Object;
      #420 REF_invokeSpecial Main$.main$$anonfun$2$$anonfun$2$$anonfun$1:()I
      #50 ()I
      #51 5
      #52 1
      #50 ()I

And decompilers show much nicer output for S2: Main$.java/Main$.java - Decompiler.com than for S3 Main$.java/Main$.java - Decompiler.com

But I guess it does not change much.

2 Likes