Swiftコンパイルを高速化する方法に関する継続的な調査。 セマンティックアナライザーのunexpected笑と予期しないプロジェクト設定。
逃した人のための最初の部分へのリンク 。
エントリー
こんにちは、紳士、開発者。 最後の投稿をバイパスしなかったすべての人に感謝します。フィードバックを受け取ることは非常に良かったです。 この記事をお楽しみください。 長い前奏曲がなければ、私は言います。今日、ガードやif-elseなどのさまざまなオペランドのパフォーマンス分析はありません。 もっと面白いものが見つかりました。
古いバグは新しい2つよりも優れています
アップル、私はそれを修正しました、それは再び12時間になるでしょう!
冗談です、ただ間違ったタイプです。 配列ではなく、辞書があります。 ただし、コンパイラはそのようなトリックに激怒しています。 これは悪い指標ですが、結局私たちは間違っていました。
エラーを修正しましょう。 配列の代わりに辞書を置きます。
誰が光を消したのですか? セグメンテーションエラー、バックライトオフ、コンパイラデッド。 このような症状がある場合、クラッシュする迅速な型チェッカーがありますが、どのようなKey-Valueがそれから期待できるのかを理解することはできません。 アセンブリからレポートを入力すると、それについてのスタックトレースが表示されます。
いいね これにより何が得られ、どのように使用できますか? この質問に答えるには、少し理論をダウンロードする必要があります。
Swiftコンパイラは、いくつかのモジュールで構成されています。
- パーサー
後続の解析表現( AST )に便利なようにコードを解析します。 これは、記述したポリッジをセマンティックアナライザーで読み取り可能にするために必要です。 - セマンティック分析
結果の表現を解析し、そのタイプセーフ(!)ASTをある種の特別なリンゴの魔術にします。 - シルジェネレーター
中間Swiftコードの生成とその最適化に従事。 中間コード-このコードはもはや人間には理解できませんが、マシンにはまだ明確ではありません。 しかし、コンパイラにはちょうどいいです。 - LLVM IR生成 。
LLVM自身の中間コードを生成します。 - LLVM
オブジェクトファイルを直接作成します。
グラフィカルに、このシーケンスは次のようになります。
各用語を急いでグーグルするのではなく、Swiftコンパイラーがモジュールで構成されていることを知る必要があります。
@Johanは 、前の記事へのコメントで、問題は型推論にあると正しく示唆しました。 これがセマンティックアナライザーのタスクです。 前の記事で確認したすべてのブレーキは、それに関連しています。
そのため、Swiftは型キャストを苦痛に感じていることがわかりました。 脚がどこから伸びているかがわかったので、これを利用して、辞書の形式を明確に示しましょう。
let myCompany: [String: [String: [String: String]]] = [ "employees": [ "employee 1": ["attribute": "value"], "employee 2": ["attribute": "value"], "employee 3": ["attribute": "value"], "employee 4": ["attribute": "value"], "employee 5": ["attribute": "value"], "employee 6": ["attribute": "value"], "employee 7": ["attribute": "value"], "employee 8": ["attribute": "value"], "employee 9": ["attribute": "value"], "employee 10": ["attribute": "value"], "employee 11": ["attribute": "value"], "employee 12": ["attribute": "value"], "employee 13": ["attribute": "value"], "employee 14": ["attribute": "value"], "employee 15": ["attribute": "value"], "employee 16": ["attribute": "value"], "employee 17": ["attribute": "value"], "employee 18": ["attribute": "value"], "employee 19": ["attribute": "value"], "employee 20": ["attribute": "value"], ] ]
コンパイル時間 :30 ms。 90ミリ秒でした。 3倍の加速。
成功。 私たちは、コンパイラの機知に頼るよりも、常に明示的にtypesを指定する方が良いと結論します 。 ところで、欠点があります。 型が正しくない場合、コンパイラが送信先を理解するまでに時間がかかります¯\ _(ツ)_ /¯
それでも疑問が生じるかもしれませんが、辞書/配列クラスのリテラル表示と明示的な表示に違いはありますか? 私はすぐに言います-これは同じことであり、コンパイルと実行時間には影響しません。 しかし、読みやすくするために、リテラルを使用することをお勧めします。
Swiftのネストされた辞書はオプションであるため、次の句読点を取得できます。
var myCompany: [String: [String: String]?] = [ "employees": [ "attribute" : "value"] ] let = myCompany["employees"] print(?!)
そしてコンパイルします。
さらに高速
最適化を含めるとアセンブリが遅くなるというステレオタイプがあります。 ただし、 コンパイラのドキュメントを読むと、逆のことがわかります。
デフォルトでは、コンパイラは各ファイルを個別に収集します。 デフォルト設定でビルドログを開くと、各Swiftファイルのレポートが個別に表示されます。
最適化設定には、whole-module-optimizationなどのフラグがあります。 このフラグを使用すると、コンパイラーはプロジェクト全体を考慮し、使用可能なすべての関数を全体として認識し、不要な型キャストを節約します。
このフラグを使用してコンパイルする場合、すべてのSwiftクラスのアセンブリが1つの操作に結合されます。
今すぐパフォーマンスを比較しましょう。 抽象テストプロジェクトを取ります。 それを当社のオープンソース作品の1つにしましょう。 コードの純度とソリューションの天才によって区別されることはありません。
最適化せずに最初にビルドしましょう:
コンパイル時間 :84秒。
次に、モジュール全体の最適化を有効にします。
コンパイル時間 :52秒。 40%を獲得!
当然、これはパフォーマンスにもプラスの効果があります。 私の個人的な経験では、これにより全体のパフォーマンスが最大10%向上します。
いつものように、「キャッチは何ですか?」という質問をする価値があります。 アプリケーションを起動し、ブレークポイントでパークすると、デバッガーは次のことを通知します。
点字で:「プロジェクトは最適化されてコンパイルされました-ステッピングは奇妙に動作する場合があります;変数が利用できない場合があります。」
翻訳:「プロジェクトは最適化されてコンパイルされました-デバッグのステップは奇妙に動作するかもしれません;一部の変数が欠落しているかもしれません。」
ポイントは、最適化中に、未使用の変数とデッドコードが削除されるため、奇妙なデバッグエラーが発生する可能性があることです。 実際、変数が突然消えたり、デバッガーがブレークポイントで停止したがらないなどの状況がしばしば発生します。
「高速」パラメーターは、Xcode最適化設定の全モジュール最適化に常に関係しています。 それを取り除く必要があります。 最適化を「なし」に戻しますが、今度はプロジェクト設定でWMOを有効にしてみましょう。
これを行うには、user-defined-settingsにフラグSWIFT_WHOLE_MODULE_OPTIMIZATION = YESを追加するだけです。
これを行うには、プロジェクトのビルド設定に移動し、上部パネルでプラスを選択します。 ドロップダウンメニューで、ユーザー定義の設定をクリックし、通常の行に値を入力します。
結果:
コンパイル時間 :19秒。 最初は1.5分でした。
ビルド速度が大幅に跳ね上がり、デバッグ中に警告が消えました。 ただし、本格的な最適化には特定の制限があります。 たとえば、フラグ 'HEADERMAP_USES_VFS'( GxocTで推奨 )を使用することはできません。これにより、インクリメンタルコンパイルの問題が解決される場合があります。 しかし、速度の面では、それがそうです。
結論 :互換性のないフラグを使用しない場合、モジュール全体の最適化によりコンパイルプロセスが大幅に高速化されます。
冗談として。
最後の記事へのコメントで、彼らはSwiftがインクリメンタルコンパイルを持っていると書いています。
実験のために、私は最初にプロジェクトを組み立て、既存の方法に1つのプリントを追加しました。 以下がその結果です。
誰も見る機会がない:50秒がゼロから構築され、45秒が増分。
stackoverflowによると、Xcode 8.2ではこれらの問題は修正されます。 残念ながら、ベータ版の私のプロジェクトは予定されていません。 そのため、公式リリースを待ちます。
これは要約する2番目の部分です。 その中で、コンパイラがスローダウンする主な理由を見つけ、アセンブリを高速化するオプティマイザフラグを見つけ、Swiftでのインクリメンタルコンパイルの不完全性も観察しました。
次の記事では、インクリメンタルコンパイルを修正する方法と、プロジェクトを高速化する一般的な方法について説明します。
謝辞:
GrimMaple-コンパイラの構造に関する相談と、文言の選択を支援します。