Scalaプロジェクトのコンパイル時間を短縮するヒント

April 10, 2023

はじめに

まずは、前提として理解しておくべき事項を説明します。

ライブラリ依存関係の解決

sbt を用いて compile を実行すると、必要に応じてライブラリの依存関係解決(update)が行われます。 依存関係解決が完了した後、ソースコードのコンパイルが開始されます。 そのため、compileが遅い場合は、依存関係解決が遅いのか、ソースコードのコンパイルが遅いのかを判断する必要があります。

ここで、ライブラリの依存関係解決とは、プロジェクトが必要とする外部ライブラリをダウンロードし、プロジェクト内で利用可能にすることを意味します。 ただし、ライブラリの依存関係が解決済みの場合は、キャッシュされた結果が使用されます。 ライブラリの依存関係のキャッシュは、デフォルトでユーザーのホームディレクトリ内の.ivy2ディレクトリに保存されます。

増分コンパイル

sbt では増分コンパイルが採用されています。増分コンパイルとは、変更されたソースコードのみをコンパイルし、変更されていないコードは前回のコンパイル結果を再利用する方法です。 これにより、コンパイル時間が短縮されます。一方、前回のコンパイル結果を再利用せずフルコンパイルを行う場合は、cleanを実行して前回のコンパイル結果を削除することで実現できます。 コンパイル時間を計測する際は、フルコンパイルの時間を測定する必要があります。

コンパイル時間の計測方法

sbt のオプション -Dsbt.task.timings=true を使用して、簡易的な所要時間の計測が可能です。

コンパイルが遅くなる代表的な要因と改善案

コンパイルが遅くなる代表的な要因と改善案は以下のとおりです。

CPU の性能を向上させる

まず、コンパイル環境の問題ですが、マシンのスペックが低いとコンパイル時間が長くなります。 経験上、高速化のカギを握るのは CPU の性能です。 適切なメモリ割り当て量があれば、メモリの増減は大きな影響を与えません。 従って、予算に余裕がある場合は、CPU の性能を向上させることが効果的です。

ファイルシステムのパフォーマンスを向上させる

コンパイル中には多くのファイルが読み書きされます。ファイルシステムのパフォーマンスが低いと、コンパイル時間が長くなります。 例えば、Windows マシンの WSL2 で Docker を使う場合、Windows ファイルシステムにファイルを配置し、それをバインドマウントしてファイルアクセスを行うと、パフォーマンスが大幅に低下します1

ソースコードを減らす

単純に言えば、ソースコードが多ければ多いほどコンパイル作業が増えます。 長期間運用されているプロジェクトはデッドコードが多い傾向があります。これを徹底的に削除することで、コンパイルを高速化できる可能性があります。

マクロの使用を減らす

マクロはコンパイル時に実行されるタスクです。マクロが多いほどコンパイル時のタスクが増えるため、コンパイル時間が長くなります。 例えば、マクロはボイラープレートコードの実装を回避するための有用なツールですが、マクロを多用している場合は別の方法に置き換えることで、コンパイル時間の短縮ができる可能性があります。

implicit の使用を減らす

implicit が多用されると、Scala コンパイラは暗黙の引数や変換を解決するために多くの探索を行う必要があり、コンパイル時間が長くなる可能性があります。implicit は有用な機能ですが、適切な使用法を選択することが重要です。

サブプロジェクト間の依存関係を最適化する

sbt はデフォルトで並列実行されますが、サブプロジェクト間に依存関係がある場合、依存先がコンパイル完了するまで依存元のコンパイルは開始されません。 依存関係の設定を最適化することで、プロジェクト全体のコンパイル時間を短縮できる可能性があります。

インターフェースと実装を分ける

インターフェースと実装を分離することで、依存関係が単純化され、コンパイルが高速化される可能性があります。また、再コンパイルの範囲を限定的にすることもできます。

まとめ

コンパイルの遅さを改善するためには、いくつかの要因を検討する必要があります。 具体的には、環境やハードウェアの最適化、ソースコードの整理、マクロや implicit の適切な使用、依存関係の最適化、およびインターフェースと実装の分離が挙げられます。 これらの要因を検討し、適切な改善策を適用することで、プロジェクトのコンパイル時間を短縮できるでしょう。

脚注


Written by Gayamasan who lives and works in Japan.