IntelliJ Scala Plugin 2020.3 Is Out!
Most releases are focused on editor features, but the stars of this show are parallel compilation and compilation charts. We also have a handful of editor goodies in store. Let’s take a closer look.
According to the official Scala Developer Survey, long compilation times is the main pain point in Scala programmers’ daily workflows by a large margin.
Although speeding up the Scala compiler is a worthy goal and Scala compilation speed is constantly being improved, the most straightforward solution is to employ parallel compilation. This solution is also the most effective. By using
N cores instead of just one, the compilation time can be reduced by up to
N times, rather than by just a few percent.
Modern computers with multi-core processors, large amounts of memory, and solid-state drives are certainly up to the task. So the Scala Compile Server now compiles independent modules in parallel:
We’ve adjusted the default VM options to support up to 4 compilation threads by default, which can speed up compilation significantly. If your machine has lots of CPU cores and lots of RAM, feel free to tweak the default values in Settings | Build | Compiler | Scala | Compile Server (accessible via Configure on the Compile Server icon) to improve compilation times even more.
To increase the efficiency of parallel compilation, you may want to optimize the structure of your project modules and the VM options of the Compile Server. But without clear guidance, this task turns into a guessing game. While you can look at the module dependency diagram, it’s hard to say how those modules would be scheduled depending on the number of compilation threads.
Furthermore, the compilation times of Scala files can vary widely, so it’s hard to tell how long it takes to compile a particular module based solely on the number of source files. The same goes for the maximum VM heap size: is it too small or too large? How does the number of compilation threads affect RAM consumption?
To help you with this task, we’ve added Compilation Charts as a Chart node in the Build tool window:
Now you can tell at a glance when there is potential for optimization. Here are the typical cases:
A module acts a bottleneck. If many modules depend on one that takes a long time to compile, consider splitting that module into several parts. This approach is especially suitable if your project consists of just one or a limited number of large modules.
Besides making your project highly parallelizable, fine-grained modules can also speed up single-thread compilation (by making it easier for the compiler to resolve references), reduce the maximum amount of memory consumed (because the compiler doesn’t have to load all the sources at once), clean up IDE autocomplete (as there are no superflous classes in the scope), and untangle dependencies between project classes (see the Rule of least power).
Compilation is thread-bound. If you see that all threads are occupied most of the time, try to gradually increase the maximum number of threads. But keep the actual number of CPU cores / threads in mind — save a thread or two for the OS and the IDE to keep the UI responsive.
Excessive number of threads. If the maximum number of threads is needed only once or twice during compilation, and the additionally compiled modules are relatively small, then the gain is only moderate but you have to allocate extra RAM that is unused most of the time. In such cases, consider reducing the maximum number of threads.
Insufficient heap size. If heap size remains close to its maximum, the VM might resort to excessive garbage collection. Even if there is no
OutOfMemoryError, compilation performance may be less than optimal. Consider increasing the maximum heap size.
Excessive heap size. Your OS can find a better use for that memory, such as the file system cache, which can actually improve the speed of compilation.
With faster compilation and dynamic charts to hold your attention, the “Compiling” comic is now a thing of the past.
Enhanced Package Prefixes
The Scala plugin can now combine IntelliJ IDEA’s package prefixes with Scala’s chained package clauses and relative imports. Although each of these features is rather obscure, instead of exponentially increasing the level of obscurity, this combination provides an intuitive and internally consistent system:
You no longer have to put up with chains of empty directories or repeat the same package prefix over and over again. Now you can keep your directories and packages neat and tidy.
We’ve descibed the rationale and the technical details in a dedicated blog post. Feel free to take a look.
The Scala plugin already supports JUnit, ScalaTest, uTest, and Specs2. It’s about time we added another entry to this list:
Support for MUnit is now available with all the usual advantages:
Scala 3 Improvements
The IntelliJ Scala plugin has already included support for the upcoming Scala 3 for quite a while, and we’re working hard to keep up with all the new updates. Most notably, the Scala plugin now understands the new syntax for main methods:
Engineering Hello Worlds has never been easier!
Because Scala 3 is still a work in progress and the API is not yet stable, we can realistically support only one version of Scala 3. We recommend using Scala plugin nightly builds to get "the latest and greatest" features as soon as possible. Select Nightly builds in Settings | Languages | Scala | Updates | Channel (you can revert to a more stable build in the same way at any time).
There are many more things in this release, including performance improvements and bug fixes.
Since you’re also updating IntelliJ IDEA, you can take a look at the IDEA What’s New page to learn about the platform features that are also relevant for Scala. Be sure to check out Code With Me – a service for collaborative development and pair programming that enables you to share the project you currently have open in your IDE with others and work on it together in real time. For more detailed information, see the Code With Me blog posts.