Slide 1

Slide 1 text

Scalaプロジェクトの ビルド速度改善 ScalaMatsuri 2024 2024-06-08 1 / 78

Slide 2

Slide 2 text

X(旧twitter) @xuwei_k github @xuwei-k blog https://xuwei-k.hatenablog.com 2 / 78

Slide 3

Slide 3 text

誰? Scala 2.8出る直前くらいから使ってる 2009年 仕事でのScala歴が13年以上? Scala 2本体その他色々なScala関連OSSの権限持 って貢献してる 3 / 78

Slide 4

Slide 4 text

前提の話 build速度とは? 扱わないこと、方向性 速度改善の目的 4 / 78

Slide 5

Slide 5 text

前提の話 どれが効果があるか?優先度高いか?は難しい 手法をひたすら色々紹介していく流れ 5 / 78

Slide 6

Slide 6 text

前提の話 そもそも普通にボトルネック探したらScalaやsbt に限らない部分の最適化の余地の方が多い場合も 多々 6 / 78

Slide 7

Slide 7 text

OSSか、それ以外か よほど巨大で活発でなければOSSではあまり問題に ならない? OSSの種類にもよる 広い意味でIOがないライブラリなら楽 7 / 78

Slide 8

Slide 8 text

OSSか、それ以外か OSSは大抵GitHub Actionsなどで無料の範囲で済 む よってOSSは富豪的に並列実行してることが多々 普通の?プロダクト開発とは全然異なる 8 / 78

Slide 9

Slide 9 text

普通の?プロダクト開発 通常CIのお金的コストがかかる 複数人で活発に開発 される場合がある CIは普通は速く終わるほど良いはず 9 / 78

Slide 10

Slide 10 text

扱うこと compileの速度とtest実行、その他含めた速度? ローカルにも恩恵はあるが主にCIでの速度 10 / 78

Slide 11

Slide 11 text

扱うこと 自分の経験や普段使用してる技術を元に話すので、 全てを網羅してるわけではない 主にsbt前提 他でも使えるがGitHub Actions(のような)CIの システム想定 11 / 78

Slide 12

Slide 12 text

目的やトレードオフ 速くなればお金はかかっていいのか? 速くなればビルドの設定が複雑になってもいいの か? 12 / 78

Slide 13

Slide 13 text

目的やトレードオフ 一時的に速くなっても継続的にメンテ不可能なほど ビルドが複雑になってしまったら駄目では 13 / 78

Slide 14

Slide 14 text

目的やトレードオフ 使うお金を増やさずに速度も全体の効率も改善した い? build速度改善のために、どの程度人間の工数かけ るのか? 14 / 78

Slide 15

Slide 15 text

扱わないこと Bazel gitのproject分ける Compiler内部の深い話 15 / 78

Slide 16

Slide 16 text

Bazelのような 差分buildとtest 究極的な理想としてはこれ 本気で巨大なmonorepoで頑張るなら潜在的な可能 性としては1番良い? 16 / 78

Slide 17

Slide 17 text

Bazelのような 差分buildとtest 2024年の時点のsbtで正確に徹底的にやるには厳し い部分が多い sbt 2にはそういう機能をもっと入れたいらしい 17 / 78

Slide 18

Slide 18 text

GitのProject分ける? monorepoのメリットは捨てたくない 18 / 78

Slide 19

Slide 19 text

基本 sbtのproject分ける ボトルネックの予想、把握、計測 sbtの設定見直す CIの設定見直す 19 / 78

Slide 20

Slide 20 text

sbtのproject を適切に分ける 一番大事かもしれない Scala compiler内部は一部しか並列化されていな い 大まかにはScala 2も3も同様 20 / 78

Slide 21

Slide 21 text

sbtのproject を適切に分ける CPUコアやメモリやネットワーク、ディクスのIOが ボトルネックでないなら、分割すればある程度まで スケールする 分割をし過ぎると別の辛さも発生する 21 / 78

Slide 22

Slide 22 text

sbtのproject を適切に分ける 分割する単位の目安は? 1つのsub projectで数万行単位? 22 / 78

Slide 23

Slide 23 text

sbtのproject を適切に分ける 本来は速度のためではなくコードの意味で分割する べき 個人的な経験として、少なくともsub project数は 百程度ならまだ普通に扱える 23 / 78

Slide 24

Slide 24 text

-Dsbt.traces=true 24 / 78

Slide 25

Slide 25 text

sbtで絶対に使うべき taskごとのtrace機能 https://github.com/sbt/sbt/pull/4576 デフォルトだと細かいtask全部記録して少し扱いづ らいので独自に改造して使ったりしてる 25 / 78

Slide 26

Slide 26 text

sbtで絶対に使うべき taskごとのtrace機能 それをGitHub Actionsでより手軽に記録して見る 方法も https://xuwei- k.hatenablog.com/entry/2024/06/01/103536 26 / 78

Slide 27

Slide 27 text

余計なsub project間の 依存削除や整理 必要ないのにdependsOnしない 必要ないのに "test->test" の依存をしない 27 / 78

Slide 28

Slide 28 text

基本? sbtやScalaはそれぞれ最新版を使う どの程度速度に直結するか?というと場合によるが 28 / 78

Slide 29

Slide 29 text

テストの並列度の理解や設定 https://xuwei- k.hatenablog.com/entry/2020/03/14/234758 https://xuwei- k.hatenablog.com/entry/2024/05/03/101725 29 / 78

Slide 30

Slide 30 text

テストの並列度の理解や設定 sub projectごとにforkするかしないか?がとて も重要 それによって設定も変わる 適切に並列実行するには、DBなどの外部のものが衝 突しないようにする工夫 30 / 78

Slide 31

Slide 31 text

DBなどの外部リソース もはやScalaやsbtに限らない話 適切な単位、タイミングで立ち上げて、それらの CPUやメモリやコネクション数も見てみる 31 / 78

Slide 32

Slide 32 text

DBなどの立ち上げ方 https://testcontainers.com testcontainersは便利だが、多過ぎると効率悪い 32 / 78

Slide 33

Slide 33 text

DBなどの立ち上げ方 複数のミドルウェアを使うなら、それを並列で立ち 上げるみたいな地味な工夫 数秒から数十秒節約 33 / 78

Slide 34

Slide 34 text

sbtはcommandを避け 全てtaskに commandは合成可能ではない commandは並列実行できない taskにしておけば並列実行可能 34 / 78

Slide 35

Slide 35 text

sbtはcommandを避け 全てtaskに sbt "all task1 task2 task3" のようにすれば可能な範囲で並列実行 inputTaskはtoTaskメソッドでtaskに変換可能 35 / 78

Slide 36

Slide 36 text

sbtを立ち上げる回数を 出来るだけ減らす 1回立ち上げるごとに、数秒から、(巨大だと)数十 秒無駄になる もちろん、1回に詰め込み過ぎるとわかりづらくな るデメリットはある 36 / 78

Slide 37

Slide 37 text

wartremoverやscalafix のためのsemanticdb生成 遅い 他に代表的なlinterやcompiler plugin? 🤔 ある程度使うと、compile速度の半分がそれにな る!? 37 / 78

Slide 38

Slide 38 text

wartremover ここ数年自分がほぼメンテしてる 詳細は何度も過去に話をしたのでそちら参照 38 / 78

Slide 39

Slide 39 text

以前のscalafixや wartremoverの話 https://xuwei- k.github.io/slides/Matsuri-2020/ https://xuwei- k.github.io/slides/matsuri-2023/ https://xuwei- k.github.io/slides/wartremover-3/ 39 / 78

Slide 40

Slide 40 text

wartremover Scala 3の場合は、compileと別のタイミングで実 行可能だが、色々辛い compileと同時の時より遅い compileと同時とは稀に違う結果になる? 40 / 78

Slide 41

Slide 41 text

scalafixのための semanticdb生成 scalafixはSyntacticRuleならあまり影響ない 有効と無効を簡単に切り替えられるようにした方が いい 例えばCIでは有効化して、デプロイ時には無効化な ど? 41 / 78

Slide 42

Slide 42 text

sbtの細かいテクニック set key := value は若干遅い? インタラクティブにsettingを変更する方法 ローカルでたまに使うには便利だが 42 / 78

Slide 43

Slide 43 text

sbtの細かいテクニック sbt内部でのトポロジカルソートやり直し? sbt -Dkey=value で指定し sys.props で取得 がいい? あるいは環境変数 43 / 78

Slide 44

Slide 44 text

sbtのExtracted.runTaskメ ソッド避ける そもそもsbtに多少詳しい人しか使わないと思う 同様のrunInputTaskなども 直接taskを実行できて便利だが、競合したり、無駄 に同じtaskが複数回実行される危険がある 44 / 78

Slide 45

Slide 45 text

sbtのrunTaskメソッド避ける 面倒でも必要に応じてDef.taskDynなど使いつ つ、普通のtaskとして依存関係を持たせて定義や実 行するべき 45 / 78

Slide 46

Slide 46 text

使ってないコードを 効率よく消す方法 直接速度に影響するほど大量でなければ、そこまで 大きく変わらないが 速度以外の側面でも無駄なコード少ないに越したこ とはない 46 / 78

Slide 47

Slide 47 text

使ってないコードを 効率よく消す方法 最近はIntelliJ IDEAが賢いのでそれ使うとわり と見つかる scalaのcompiler option指定でも 47 / 78

Slide 48

Slide 48 text

IntelliJ IDEAの使ってない コード検知機能 そのproject内部でグローバルに検知してくれる 多少誤検知ある companion objectにimplicitでinstance定義 したときにobjectが使ってない判定される 48 / 78

Slide 49

Slide 49 text

使ってないコード効率よく消す 自作ツール例 https://xuwei- k.hatenablog.com/entry/2023/02/08/115510 https://github.com/xuwei-k/unused-code https://xuwei- k.hatenablog.com/entry/2022/02/14/152706 49 / 78

Slide 50

Slide 50 text

compile速度 macroが必ず遅くなるわけではないが、結果的に macroが原因なことは多い compiler pluginも種類によっては注意 circeのautoのような再帰的に導出するものは、裏 で重複して展開される可能性が高まるって爆発する ので絶対ダメ 50 / 78

Slide 51

Slide 51 text

compile速度のプロファイル https://github.com/scala/scala/pull/7364 Scala 2のChrome traceを1度は実行してみまし ょう -Yprofile-trace 51 / 78

Slide 52

Slide 52 text

compile速度のプロファイル Scala 3にも入る? https://github.com/scala/scala3/pull/19897 52 / 78

Slide 53

Slide 53 text

Scala 3のinlineでの compile速度爆発との戦い 前にblog書いた https://xuwei- k.hatenablog.com/entry/2022/03/30/142529 53 / 78

Slide 54

Slide 54 text

compile速度のプロファイル Scala 3用で独自に作ったが、公式に前述のものが 入れば必要なくなりそう https://xuwei- k.hatenablog.com/entry/2022/11/12/160933 https://github.com/xuwei- k/scala3profile 54 / 78

Slide 55

Slide 55 text

compileとtestでJVMの profile 個人的によく使ってる https://visualvm.github.io/ その他JVMには色々あるので使えるものはなんでも 使おう 55 / 78

Slide 56

Slide 56 text

testでJVMのprofile 余計なスレッドや、スレッドプール、コネクション プールが作られ過ぎていないか? ActorSystem気軽に作ると結構無駄なので注意 56 / 78

Slide 57

Slide 57 text

testでのprofile CIでは、JVMとその他含めてメモリが効率的に使え ているか? 意味なくメモリなどを使い過ぎているプロセスはな いか? JVMには色々なツールが揃ってるのでフル活用 57 / 78

Slide 58

Slide 58 text

CI上での計測 GitHub Actionsだと、例えばこういうものも https://github.com/catchpoint/workflow- telemetry-action ただし、細かい部分不満がある、一部正しく取れな い 58 / 78

Slide 59

Slide 59 text

CIでのキャッシュ効率 独自にS3保存などの方がいい場合も? https://synamon.hatenablog.com/entry/2022/11 keyの設計を必ずしっかり https://xuwei- k.hatenablog.com/entry/2020/03/16/005728 59 / 78

Slide 60

Slide 60 text

CIでのキャッシュ効率 GitHub Actions公式の制約に注意 容量の制約 default branchで保存しないと他で使えない しっかりキャッシュがヒットしてるか定期的にログ などで確認 60 / 78

Slide 61

Slide 61 text

CIで並列job お金で時間を買う方法 効率は犠牲になるが、ある程度までなら簡単に速く なるので、人間が工夫を頑張る工数は節約 テスト名のhashでやるなど https://labs.septeni.co.jp/entry/2018/11/23/ 61 / 78

Slide 62

Slide 62 text

CIで並列job compileは1つでその後testなどは分岐? その場合にsbtのremote cacheではなく workspace丸ごとuploadしたほうが速い、といっ たノウハウ あるいは、相対的にcompileのコストが低いなら、 最初から富豪的にcompileも重複して実行? 62 / 78

Slide 63

Slide 63 text

sbt-projectmatrix の使用検討 https://github.com/sbt/sbt- projectmatrix デフォルトではsbtは "++ 3.x" などでscala versionを切り替えるが、これは状態を変更してい る 状態を変更しないと他のversionでbuildが出来な いのはデメリット 63 / 78

Slide 64

Slide 64 text

sbt-projectmatrix の使用検討 Scalaのversion以外でも他にも色々な意味で cross buildしたい場合 sbt 2ではこれがデフォルトになる? 普通に設定したら並列にできないものまで並列で可 能になるので 64 / 78

Slide 65

Slide 65 text

sbt-projectmatrix の使用検討 全体としての効率重視したい場合? CIのjob分けるより、効率はいいはず? 65 / 78

Slide 66

Slide 66 text

sbtの concurrentRestrictions 以前のテストの並列の話のblogにも書いたが デフォルトのままではなく細かく変えた方がいい場 合は多々ある 66 / 78

Slide 67

Slide 67 text

個々のtest時間を集計してみる 例えばscalatestだと -oD のようなオプションが あります それをさらにいい感じに集計しないと・・・ これだけでボトルネックわかるか?というと 67 / 78

Slide 68

Slide 68 text

pipelineの有効化検討 ThisBuild / usePipelining := true Scala 2にも3にもある 3に入ったのは最近(2024年4月) https://github.com/scala/scala3/pull/18880 68 / 78

Slide 69

Slide 69 text

pipelineの有効化検討 今回のScala祭で話がある? 普通のcompile option設定ではなく、build toolと連携した特別な設定が必要 69 / 78

Slide 70

Slide 70 text

pipelineの有効化検討 最大、数割程度速くなる可能性? まだそこまで広く使われてないので、罠にハマる可 能性もありそう 70 / 78

Slide 71

Slide 71 text

buildファイルの Scala書き過ぎない sbtが多少?好きな人以外は、そもそもそこを頑張 らないと思うが 使わない部分も必ずcompileされるので sbtとしてのbuildファイルに書く必然性がないも のは他の場所に 71 / 78

Slide 72

Slide 72 text

使っているsbt plugin見直す 稀にsbt pluginせいで遅いことがある。あった ほぼ使ってないものや、メンテされてない古いもの は場合によって脱却しよう maven centralではなく古い非推奨なrepo.scala- sbt.orgのresolverに置いてあるものも注意 72 / 78

Slide 73

Slide 73 text

テストの依存の工夫 https://xuwei- k.hatenablog.com/entry/2022/10/09/124418 73 / 78

Slide 74

Slide 74 text

数日前書いた記事 Scalaやsbt直接関係ない build時間の統計取ったり様々な角度から https://xuwei- k.hatenablog.com/entry/2024/06/07/074942 74 / 78

Slide 75

Slide 75 text

まとめ Scalaのcompileそのものが原因で遅いことはない とは言い切れないが、そこだけがボトルネックにな るのは非常に稀です 仮にcompileが遅くても、CI全体で見るとtest時 間も結構な割合を占めるはずで、そこはあまり Scala特有ではない 75 / 78

Slide 76

Slide 76 text

まとめ 色々やって、それでももっと速くしたい場合は、最 終的にはお金でスケールする余地を残して、作って るプロダクトがCI程度のお金が大したことないほど 儲ければ問題ないのでは? 76 / 78

Slide 77

Slide 77 text

まとめ 今回紹介したような手法ほぼ全て試してもまだ圧倒 的に遅いと感じるならば、他の言語やbuild tool を検討した方がいい可能性はある? 思っている以上に手段は数多くあるので、目的に応 じて適度に頑張りましょう 77 / 78

Slide 78

Slide 78 text

おわり 78 / 78