Xcode 10から正式採用されたNew Build Systemの並列ビルド(並列コンパイル)について

# 概要

- Xcode 10で[New Build System](https://developer.apple.com/documentation/xcode_release_notes/xcode_10_release_notes/build_system_release_notes_for_xcode_10)が正式採用されデフォルト設定になったのですが、以前まで有効だった `IDEBuildOperationMaxNumberOfConcurrentCompileTasks` で[並列コンパイルの最大スレッド数の指定](https://chrisbrandow.svbtle.com/faster-build-times-by-leveraging-multicore-cpu?fbclid=IwAR2G0mYdI5N8wgQpxgcjX0JLtDlDCdXt0dBV--_us4RmLOfqQVVH5U4ebCM)が機能していないような挙動だったので調べてみました。

# 結論

- New Build Systemは、 `IDEBuildOperationMaxNumberOfConcurrentCompileTasks` の影響を受けません。
- New Build Systemは、デフォルトで利用可能な最大のスレッド数（Hyper-Threadingも考慮される）で並列コンパイルされます。
- つまり、並列コンパイルに関しては手動での設定が不要となりました。

# 検証

- Legacy Build SystemとNew Build Systemでビルド時間を比較してみます。
  - Xcodeのビルド時間は `ShowBuildOperationDuration` を[設定](https://qiita.com/ruwatana/items/21b25781a2f44ccf29e2#%E4%B8%8B%E6%BA%96%E5%82%99-%E3%83%93%E3%83%AB%E3%83%89%E6%99%82%E9%96%93%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B)することで表示されます。
- Build Systemの切り替えは、Xcodeのメニューの `File > Project Settings…` または `File > Workspace Settings…` から設定可能です。

<img width="534" alt="build_system.png" src="https://qiita-image-store.s3.amazonaws.com/0/16700/f1b4f606-aaf3-cdf2-8f31-cedce5ba5e77.png">

- `IDEBuildOperationMaxNumberOfConcurrentCompileTasks` で設定した値は以下 `最大スレッド数` と記載します。

## 環境

- Xcode 10.1 (10B61)
- Swift 4.2
- iMac (Retina 5K, 27-inch, 2019) Intel Core i9-9900K 3600 MHz (8 cores)

**対象のプロジェクト規模**

- RxSwiftやRealmなどを含む3人月規模のプロジェクト

## 検証1: Indexingが終わった後にビルド

### 計測方法

- DerivedDataを削除し、初回のIndexingが終わった後にビルドを開始します。

### 結果

| ビルドシステム | 最大スレッド数 | ビルド時間 |
| --- | :---: | ---: |
| Legacy | 1 | 463.963 |
| Legacy | 4 | 163.117 |
| Legacy | 8 | 123.570 |
| Legacy | 16 | 124.448 |
| New | 1 | 89.440 |
| New | 4 | 89.360 |
| New | 8 | 89.848 |
| New | 16 | 89.504 |

- *ビルド時間は3回の平均値*

### 考察

- New Build Systemは `IDEBuildOperationMaxNumberOfConcurrentCompileTasks` の影響を受けておらず、ほぼ同一の計測結果でした。
  - 調べたところドキュメント等への記載は見つけられなかったのですが、[ツイート](https://twitter.com/nuthatch/status/1091076634478825475)している人を発見。
- 少し本題からずれますが、Legacy Build Systemで8スレッドと16スレッドでビルド時間の差が出ていませんでした…（詳しくは後述）。

### 問題点

- New Build Systemのビルドが速いなーと思いつつ、何回もビルドするうちにビルド開始時のプログレスバーの初期位置が明らかにLegacyよりも進んでいることに気がつきました。そこで[Build with Timing Summary](https://www.avanderlee.com/optimization/analysing-build-performance-xcode-10/)で確認したところ、CompileSwiftSourcesがLegacyだと55 tasksだったのに対してNew Build Systemは5 tasksで、Indexingのタイミングで事前にコンパイルするタスク数の違いがあり、結果ビルド時間全体で差がついてしまったことがわかりました。

## 検証2: Indexingが開始される前にビルド

### 計測方法

- Indexingが終わった後だとCompileSwiftSourcesのタスク数に違いがあったため、今度はDerivedDataを削除し、Xcodeを起動直後のIndexingが始まる前にビルド（[Build with Timing Summary](https://www.avanderlee.com/optimization/analysing-build-performance-xcode-10/)）を開始します。

### 結果

| ビルドシステム | 最大スレッド数 | ビルド時間 | Compile C | Compile Swift |
| --- | :---: | ---: | ---: | ---: |
| Legacy | 1 | 792.610 | 341.848 | 410.295 | 
| Legacy | 4 | 214.919 | 373.098 | 327.548 |
| Legacy | 8 | 153.421 | 491.378 | 422.904 |
| Legacy | 16 | 153.310 | 827.944 | 662.014 |
| New | 1 | 142.501 | 809.741 | 643.152 |
| New | 4 | 142.652 | 808.462 | 641.644 |
| New | 8 | 142.984 | 809.148 | 636.419 |
| New | 16 | 143.610 | 822.892 | 652.259 |

- *Compile CとCompile SwiftはTiming Summaryの結果*
- *すべてケースでCompileCは1038 tasks、CompileSwiftSourcesは91 tasksで同条件なことを確認*
- *3回試行するにはビルド時間が長すぎたので1回実行した結果*

### 考察

- New Build SystemのCompile C/SwiftがLegacy Build Systemの16スレッドとほぼ同じ結果になっていたので `IDEBuildOperationMaxNumberOfConcurrentCompileTasks` を指定することなく利用可能な最大のスレッド数(16スレッド)が適用されていると言えそうです。
- New Build SystemはLegacy Build Systemより10%未満のビルド処理の向上がみられました。

# おまけ

- New Build Systemを検証するきっかけはiMacを2017モデルから2019に変えたのでどれだけビルドが高速化するかを確認したかったのですが、 `IDEBuildOperationMaxNumberOfConcurrentCompileTasks`を変更してもビルド速度に影響が出なかっため調べてみました。
- iMac 2017との比較結果は以下です。

**比較対象のMac**

- iMac (Retina 5K, 27-inch, 2017) Intel Core i7-7700K 4200 MHz (4 cores)
- iMac (Retina 5K, 27-inch, 2019) Intel Core i9-9900K 3600 MHz (8 cores)

**結果**

| CPU | ビルドシステム | 最大スレッド数 | ビルド時間 | Compile C | Compile Swift |
| --- | --- | :---: | ---: | ---: | ---: |
| i7-7700K | Legacy | 4 | 267.478 | 460.486 | 431.874 |
| i7-7700K | Legacy | 8 | 241.329 | 745.896 | 725.248 |
| i7-7700K | New | 4 | 258.462 | 727.819 | 713.259 |
| i7-7700K | New | 8 | 264.116 | 749.004 | 727.603 |
| i9-9900K | Legacy | 8 | 153.421 | 491.378 | 422.904 |
| i9-9900K | Legacy | 16 | 153.310 | 827.944 | 662.014 |
| i9-9900K | New | 8 | 142.984 | 809.148 | 636.419 |
| i9-9900K | New | 16 | 143.610 | 822.892 | 652.259 |

- i9-9900Kはi7-7700Kの1.6〜1.8倍程度の高速化となりました。
- i7-7700Kだと4スレッドから8スレッド（Hyper-Threading）に変更すると平均して10％くらい速くなっていました。ただi9-9900Kだとスレッド数を増やしても8スレッド以上はほぼ同じ結果になっていましたが、これは1コアのリソースを使い切ってしまいHyper-Threadingでパフォーマンスが向上する余地がなかったということなんですかね…？ Hyper-Threadingを使いたくてi9-9900Kにしたので少し残念でした。