Try out pipelined builds in Scala 3.5 nightly

When pipelining is turned off, it successfully compiles in sbt 1.9.9.

It fails due to a compiler crash in sbt 1.10.0-RC2, but it still happens with Scala 3.4.1, so it might be a regression from sbt.

[error] dotty.tools.dotc.core.TypeError$$anon$1: object caps does not have a member type Cap

Please understand that I cannot reveal the entire source code because it’s a proprietary software of my company.

For a bit more information, it’s a Java/Scala cross building subproject wrapping several Java Spring Boot modules as Scala ZIO services.

EDIT: It was a Zinc issue. Pipelining prevents Java compilation with compileOrder := JavaThenScala config · Issue #1352 · sbt/zinc · GitHub

2 Likes

The project is proprietary (so not on github), but its dependencies are:

  "org.apache.commons"          % "commons-email"                      %   "1.5",                                       
  "org.slf4j"                   % "slf4j-api"                          %   "1.7.32",                                    
  "ch.qos.logback"              % "logback-classic"                    %   "1.2.5",                                     
  "org.codehaus.janino"         % "janino"                             %   "3.1.4",                                     
  "com.typesafe.scala-logging" %% "scala-logging"                      %   "3.9.5",                                     
 ("com.typesafe.akka"          %% "akka-slf4j"                         %   "2.6.20").cross(CrossVersion.for3Use2_13),   
 ("com.typesafe.akka"          %% "akka-stream"                        %   "2.6.20").cross(CrossVersion.for3Use2_13),   
 ("com.typesafe.akka"          %% "akka-actor"                         %   "2.6.20").cross(CrossVersion.for3Use2_13),   
 ("com.typesafe.akka"          %% "akka-actor-typed"                   %   "2.6.20").cross(CrossVersion.for3Use2_13),   
 ("com.typesafe.akka"          %% "akka-stream-typed"                  %   "2.6.20").cross(CrossVersion.for3Use2_13),   
 ("com.typesafe.akka"          %% "akka-protobuf-v3"                   %   "2.6.20").cross(CrossVersion.for3Use2_13),   
 ("com.typesafe.akka"          %% "akka-http-core"                     %  "10.2.10").cross(CrossVersion.for3Use2_13),   
 ("com.typesafe.akka"          %% "akka-http"                          %  "10.2.10").cross(CrossVersion.for3Use2_13),   
 ("com.lightbend.akka"         %% "akka-stream-alpakka-mqtt-streaming" %   "3.0.4" ).cross(CrossVersion.for3Use2_13),   
 ("org.scalaj"                 %% "scalaj-http"                        %   "2.4.2" ).cross(CrossVersion.for3Use2_13),   
  "com.softwaremill.quicklens" %% "quicklens"                          %   "1.9.7",                                     
  "devlaam"                    %% "coco"                               %   "0.8.1",                                     
  "s2a"                        %% "lysinecore"                         %   "0.4.29"                                     

and i compiled on a system with 6 cores / 12 threads. They are running all above 90% load most of the time. There are no macro’s (all removed after the transition from Scala 2 to Scala 3).

I also tried to compile my opensource Leucine project but that fails with:

sbt.librarymanagement.ResolveException: Error downloading org.scala-native:nscplugin_3.5.0-RC1-bin-20240416-63810dc-NIGHTLY:0.4.17

however, since this project has no dependencies, it is probably of less interest.

BTW, thank you for your efforts to keep on improving the compiler this way. :+1:

@devlaam I think I wasn’t clear sorry, by dependencies, I mean a subproject dependency (e.g. established with dependsOn).

so A -> B -> C -> D will benefit more than A & B & C -> D if A, B, C are similarly sized projects

1 Like

How much time reduced on scala/scala3 itself,is there number to share?

Just my 2 cents.
I have a small project GitHub - sacode387/FlowRun: Executable flow diagrams
Tested with sbt "clean; compile", got from ~22s to ~20s.
Tested with sbt and then clean; compile, got from 9s to 6s.
So seems like in interactive mode it’s around 30% improved.

1 Like

Slightly bigger project: GitHub - sake92/hepek: Typesafe HTML templates and static site generator in pure Scala
cold start compile: 34s → 36s
interactive sbt: 10s → 17s

In this case, for some reason it is a bit slower.

1 Like

Ahh, clear now, i don’t have these.

I would recommend cycling through clean -> compile a few times to get rid of effects such as checking for dependency updates, and give a chance for JIT compilation to make compiler performance be more deterministic.

I checked out the project sake92/hepek as you mentioned and from a cold boot for both pipelining and not, ran clean;hepek/compile over and over in the shell. After enough cycles pipelining was consistently 4-5 seconds, and not pipelining 6-7 seconds.

I gave a talk at func prog sweden,

but I have the slides uploaded also. I actually profiled the timestamps of when each part of the scala 3 build began, so I can present a timeline of compilation tasks:

Slide with comparison of pipelining with vanilla Scala:

Slide with comparison of outline compilation and worker threads with vanilla Scala:

its important to note that the diagrams are made from real timestamps recorded by a profiler, so we can see that sbt schedules projects to begin earlier. Because in the Scala 3 build the majority of time is spent in the final project, it does not benefit much from pipelining, but when activating the outline typing mode and parallel worker threads (still WIP) we get 33% speedup. There are other configurations we could experiment with as well.

1 Like

Thanks for sharing, hoping we see this turn on by default on every Scala project.

benchmark with scalaz

  • enable pipeline: 47.7 s
  • disable pipeline: 53.0 s
5 Likes

Hi, I’ve given this a try in our work project consisting of 7 modules (JVM + JS). After publishing the zerowaste plugin locally against this Scala version, I’ve got the following results.

Scala 3.5.0-RC1-NIGHTLY: Pipelining OFF (failed compilation)

$ time sbt "clean; compile"  
Executed in  118.12 secs    fish           external
   usr time   15.26 mins    0.00 micros   15.26 mins
   sys time    0.59 mins  838.00 micros    0.59 mins

Scala 3.5.0-RC1-NIGHTLY: Pipelining ON (failed compilation)

$ time sbt "clean; compile"  
Executed in  133.21 secs    fish           external
   usr time   17.71 mins    0.00 micros   17.71 mins
   sys time    0.65 mins  811.00 micros    0.65 mins

Scala 3.5.0-RC1-NIGHTLY: Pipelining OFF (successful compilation)

$ time sbt "clean; compile"  
Executed in  134.35 secs    fish           external
   usr time   16.25 mins    0.00 millis   16.25 mins
   sys time    0.63 mins    1.61 millis    0.63 mins

Scala 3.5.0-RC1-NIGHTLY: Pipelining ON (successful compilation)

$ time sbt "clean; compile"  
Executed in  124.09 secs    fish           external
   usr time   16.91 mins    0.00 micros   16.91 mins
   sys time    0.63 mins  801.00 micros    0.63 mins

Scala 3.3.3

For reference, here are the results with the Scala version we actually use.

$ time sbt "clean; compile"  
Executed in   75.74 secs    fish           external
   usr time  681.12 secs    0.07 millis  681.12 secs
   sys time   23.15 secs    1.01 millis   23.15 secs

I have included the results of the compilation that resulted in failure with the NIGHTLY version due to -Xfatal-warnings, but it did seem to compile the different modules in parallel.

The compilation time was slightly reduced with pipelining enabled during the successful compilations (disabling -Xfatal-warnings), but it’s considerably slower comparing it with Scala 3.3.3.

Happy to share any more relevant configuration details that could help, except the source code for obvious reasons :slight_smile:

@gvolpe Did you try to run clean/compile several times to account for any JIT compilation?, That difference between 3.3.3 and 3.5-snapshot is quite alarming - it would be really good to report anything you find to scala/scala3 on GitHub - we do have a few in-flight issues about performance problems so maybe you hit the same one

1 Like

That’s a good call, I didn’t before, so I tried again. Pipelined compilation is still as slow, but not so much difference between Scala 3.3.3 and 3.5-nightly.

In the following results, the first line always corresponds to the first execution with those settings (usually slower), the second line to the second execution and so on.

Scala 3.3.3

Executed in  137.65 secs    fish           external
   usr time   17.65 mins    0.00 micros   17.65 mins
   sys time    0.51 mins  784.00 micros    0.51 mins

Executed in   69.84 secs    fish           external
   usr time  674.95 secs    0.00 millis  674.95 secs
   sys time   24.09 secs    1.72 millis   24.09 secs

Executed in   73.79 secs    fish           external
   usr time  667.21 secs    0.00 micros  667.21 secs
   sys time   23.39 secs  955.00 micros   23.39 secs

Executed in   72.61 secs    fish           external
   usr time  684.94 secs  526.00 micros  684.94 secs
   sys time   24.10 secs  363.00 micros   24.10 secs

Scala 3.5-nightly - Pipelining OFF

Executed in  139.74 secs    fish           external
   usr time   16.80 mins    0.15 millis   16.80 mins
   sys time    0.64 mins    1.04 millis    0.64 mins

Executed in   78.43 secs    fish           external
   usr time  696.23 secs    0.00 millis  696.23 secs
   sys time   28.91 secs    1.06 millis   28.91 secs

Executed in   76.89 secs    fish           external
   usr time  688.25 secs  713.00 micros  688.25 secs
   sys time   29.01 secs  269.00 micros   29.01 secs

Executed in   77.26 secs    fish           external
   usr time  700.63 secs  704.00 micros  700.62 secs
   sys time   29.45 secs  268.00 micros   29.45 secs

Scala 3.5-nightly - Pipelining ON

Executed in  142.46 secs    fish           external
   usr time   18.51 mins    0.00 micros   18.51 mins
   sys time    0.68 mins  840.00 micros    0.68 mins

Executed in  133.74 secs    fish           external
   usr time   16.46 mins    0.00 millis   16.46 mins
   sys time    0.62 mins    1.14 millis    0.62 mins

Executed in  135.49 secs    fish           external
   usr time   17.18 mins    0.00 millis   17.18 mins
   sys time    0.66 mins    1.13 millis    0.66 mins

Executed in  138.20 secs    fish           external
   usr time   16.60 mins    0.00 micros   16.60 mins
   sys time    0.63 mins  831.00 micros    0.63 mins

Hope this helps!

@gvolpe I have to say I can’t really offer any diagnostics without suggesting you perhaps use a profiler, such as -Yprofile-enabled combined with -Yprofile-destination foo-project.csv (I recommend a destination file derived from the project name)

if you were to record that data, it can give an insight into any differences in how long compiler phases are taking, and when projects are scheduled.

I use this script to parse the output of each project into a single csv I can copy into google sheets and generate a Gantt chart:

Other options include comparing async-profiler flame charts side-by side

Thanks! I’ll see if I can squeeze some time at work to dig deeper into this, but can’t promise anything…

In the meantime, I’ve also tried it in this open source project, which has about 10 modules but it’s still smaller than my work project.

TL;DR: pipelining reduces compilation times ~16%

Scala 3.5-nightly - Pipelining ON

Executed in   50.08 secs    fish           external
   usr time  498.06 secs    0.00 micros  498.06 secs
   sys time   17.89 secs  642.00 micros   17.89 secs

Executed in   48.74 secs    fish           external
   usr time  491.49 secs    0.00 micros  491.49 secs
   sys time   16.80 secs  479.00 micros   16.80 secs

Executed in   50.76 secs    fish           external
   usr time  515.24 secs  247.00 micros  515.24 secs
   sys time   17.19 secs  239.00 micros   17.19 secs

Executed in   51.17 secs    fish           external
   usr time  506.55 secs  334.00 micros  506.55 secs
   sys time   18.00 secs  112.00 micros   18.00 secs

Scala 3.5-nightly - Pipelining OFF

Executed in   61.76 secs    fish           external
   usr time  535.70 secs  321.00 micros  535.70 secs
   sys time   17.21 secs  111.00 micros   17.21 secs

Executed in   58.85 secs    fish           external
   usr time  510.41 secs  405.00 micros  510.41 secs
   sys time   16.22 secs  140.00 micros   16.22 secs

Executed in   59.27 secs    fish           external
   usr time  504.61 secs  280.00 micros  504.61 secs
   sys time   17.00 secs  101.00 micros   17.00 secs

Executed in   58.09 secs    fish           external
   usr time  497.07 secs  338.00 micros  497.07 secs
   sys time   17.20 secs  122.00 micros   17.20 secs

Scala 3.4.1

Executed in   58.88 secs    fish           external
   usr time  511.53 secs  350.00 micros  511.53 secs
   sys time   16.83 secs  161.00 micros   16.83 secs

Executed in   59.12 secs    fish           external
   usr time  513.62 secs    0.00 micros  513.62 secs
   sys time   16.56 secs  456.00 micros   16.56 secs

Executed in   58.21 secs    fish           external
   usr time  501.87 secs   36.00 micros  501.87 secs
   sys time   17.20 secs  536.00 micros   17.20 secs

Executed in   58.70 secs    fish           external
   usr time  504.60 secs  395.00 micros  504.60 secs
   sys time   17.48 secs   18.00 micros   17.48 secs

Scala 3.3.3

Executed in   64.82 secs    fish           external
   usr time  537.94 secs  491.00 micros  537.94 secs
   sys time   15.52 secs   62.00 micros   15.52 secs

Executed in   55.82 secs    fish           external
   usr time  487.35 secs    0.00 micros  487.35 secs
   sys time   14.71 secs  384.00 micros   14.71 secs

Executed in   56.75 secs    fish           external
   usr time  496.68 secs  294.00 micros  496.68 secs
   sys time   14.28 secs  125.00 micros   14.28 secs

Executed in   57.18 secs    fish           external
   usr time  495.24 secs  292.00 micros  495.24 secs
   sys time   15.31 secs  263.00 micros   15.31 secs
1 Like

Hi Jamie,

I tried the 3.4.2 version and got some puzzling bugs with cyclic imports. And then I tried the 3.5.0-RC1 version, no bugs at all! The speed-up on GitHub - etorreborre/specs2: Software Specifications for Scala is not huge, like 10 to 15%, but what I realize is crazy is how fast the Scala compiler has become. A full recompile of the project in a hot sbt session takes now 15 or 16 seconds (with pipelining). This is a serious achievement so kudos to everyone involved!!!

Eric.

9 Likes