A frequent complaint about Scala is build times: Scala builds are too slow, and the immediate reaction is to blame the compiler. While indeed the Scala compiler is slower than the Java one, it’s not always the only culprit. We pride ourselves by going the extra mile in our support channels, and we often get asked for help for both Scala and Sbt issues where Hydra is not always involved. It’s time to share some of the lessons we learned.
1. Formatting on compile
..or any task that’s performed on compile, like scalastyle checks (or Slick table generation). Most Scala projects adhere to a coding style, and as the team grows it’s essential that you stick to it. Scalafmt and Scalastyle are both great tools, and we recommend you start with them from day 1. However, they take time and need not run on each call to
compile. Both tasks are usually not incremental, so formatting will reformat all sources, even when a single one was changed. This often takes more time than compilation itself. We recommend these tasks to be run by the Continuous Integration setup and (manually) whenever needed (usually before committing or pushing changes upstream) by each developer.
2. Cold compiler
The JVM is a lot faster when “hot”. To get hot, it needs to run for a long time so the JIT compiler has time to natively-compile and optimize the most common methods. Make sure you don’t just run
sbt compile. Use the interactive Sbt shell. Even better, build inside your IDE, which already keeps a hot JVM for you. If you’re using Maven, the 3.x version series can still use the Zinc external server, or give the new kid on the block, Bloop, a try.
3. High GC time
Compilation is memory-intensive, and as projects grow larger they tend to need more memory during compilation. Make sure you don’t have high GC times (the Hydra compiler warns you about this, so if you’re using Hydra you’re safe here). This may be the simplest and most impactful thing you can do. Each tool has different ways to specify memory options (for example, with Gradle Build Tool, it’s part of your build definition build definition), so make sure your options are picked up (again, Hydra prints the amount of memory it can use).
4. Automatic derivation
Scala macros and implicits can save a lot of typing, and nobody loves boilerplate code. Especially in serialization libraries like Circe, implicit macros can generate your readers/writers for you. It’s great when it works, but it may kick in a bit too often. Again, Hydra Monitoring will show files that look suspicious and you have the option to rewrite your code in a more direct way.
5. Not caching everything
Are you building on the CI? Yeah, we do, too. Make sure you cache
.m2/. Sbt can take a long time for a cold start and you can save minutes this way (more about this coming in a blog post of its own).