Swift 3コンパイラの概要とそれを高速化する方法。 パート1
既存の神話を暴く。 Xcodeでオートコンプリートの問題を表示します。
前書き:
当社は、ターンキーモバイルアプリケーションを開発しています。 私たちのiOS開発者の多くはロシア語よりもObjective-Cを上手に話しており、彼女はCocoaであり、iPhoneと抱き合って眠っています...そして突然Swiftで書き始めました。
構文のさまざまなショール、面白い「セグメンテーションフォールト:11」、バックライトの定期的なフェードについては説明しません。これらはすべて既知です。 それを傷つけましょうが、耐えられる。
しかし、不快感だけでなく、本当にビジネスを殺すものがあります。 遅いコンパイラ。 はい、はい、これは単なる大騒ぎの見出しではありません。
Obj-CとSwiftで同じサイズのプロジェクトを4倍の時間差で組み立てる場合。 1つのメソッドを追加するときに、コード全体の半分が再構築される場合。 コンパイラエラーが一般的にそれを無効にするとき、それは開発者の時間の本当のキラーです。 そしてご存じのとおり、時は金なりです。
選択肢は2つあります:泣き言を言い続け、耐える、または問題を解決します。 2番目を選択しました。
自転車の発明
Swiftに膝を深く突っ込む前に、私たちは以前、このトピックに関する既存の研究をテーマに、インターネットの広がりを通していたずらをしました。 幸いなことに、ロシア語で良い記事が見つかり、 英語で元の記事が見つかりました。
なぜ別のものを与えるのですか? そして、第一に、これらはすべて3回目の迅速化の前に行われ、第二に、記事の一部の文が完全に真実ではないため、sidな場所のリストを補足するのは良いことです。 何をしますか。
ここの資料は1つの記事のためではないので、徐々に広げていきます。
また、コンパイル速度自体に加えて、実行時のパフォーマンス要因もあります。これも強調する必要があります。
そもそも、すでにわかっていることですが、Swift 3で関連性を確認してください。
ゼロ合体演算子
私のお気に入りのSwiftチップはシュガーオプションです。 Obj-Cの安全な投稿のようなもの。
過去の記事の例をご覧ください。 これで、それらが完全に正しくない理由を理解できます。
let left: UIView? = UIView() let right: UIView? = UIView() let width: CGFloat = 10 let height: CGFloat = 10 let size = CGSize(width: width + (left?.bounds.width ?? 0) + (right?.bounds.width ?? 0) + 22, height: height)
コンパイル時間 :12秒! バディ、3つ目の切り株がありますか?
Swift 2.2よりもさらに悪い。
私は言いたい:「うわ、アップル、一体何だ?」、しかし結論に急ぐな。 長い式をいくつかの小さな式に分割して、このコードを少し最適化しましょう。
let firstPart = left?.bounds.width ?? 0 + width let secondPart = right?.bounds.width ?? 0 + 22 let requiredWidth = firstPart + secondPart let size = CGSize(width: requiredWidth, height: height)
コンパイル時間 :30 ms。 (ミリ秒)
だから、それはオプションの悪についてではありませんか?
しかし、いや、それは簡単すぎるでしょう。 タスクを複雑にしましょう:
class A { var b: B? = B() } class B { var c: C? = C() } class C { var d: D? = D() } class D { var value: CGFloat? = 10 } ... let left: A? = A() let right: A? = A() let width: CGFloat = 10 let height: CGFloat = 10 // ! let firstPart = left?.b?.c?.d?.value ?? 0 + width let secondPart = right?.b?.c?.d?.value ?? 0 + 22 let requiredWidth = firstPart + secondPart let size = CGSize(width: requiredWidth, height: height)
コンパイル時間 :35ミリ秒。
結論 :Nil Coalescing Operatorにはすべてが滅菌されているため、使用できます。
しかし、問題は何でしたか?
悪の根源が長い表現に潜んでいると推測することはもはや難しくありません。 ロシアの記事の著者は、nil合体演算子の問題は複雑な操作でのみ再現されるとさりげなく言及しましたが、残念ながら、これに焦点を合わせていませんでした。
ルールは次のとおりです。いくつかの複雑な用語を含む式は、コンパイラによって構築されます。 つまり、変数だけでなく、いくつかのアクションも実行するものです。 ただし、必要なだけ変数を追加できます。
「ビリー、証拠はどこですか?」
いいね 次に、前のコードを使用しますが、サブオペレーションに分割しません。
let requiredWidth = left?.b?.c?.d?.value ?? 0 + right?.b?.c?.d?.value ?? 0 + width + 22 let size = CGSize(width: requiredWidth, height: height)
結果は長く待つ必要はありませんでした(する必要がありました):
画面から読み込めなかった場合は、「表現は複雑すぎて妥当な時間内に解決できません。式を個別のサブ式に分割することを検討してください」と言います。
翻訳:「式は複雑すぎて許容できる時間内に解くことができませんでした。式を個別のサブ式に分割してください。」
Rt
さらに、理論的根拠のない観察です。
多くの人が、Xcodeでオートコンプリートが定期的に低下することに気付いています。 これは通常、バックグラウンドコンパイル時に発生します。 「 Expression was too complex 」と呼ばれる式のようなものを書いた場合、プロンプトはすぐに消えます。
これは簡単に確認できます。 同じ方法で、self.viewの作成を開始してヒントを取得します。
そして、キラー式を追加します。 それだけです。Ctrl+スペースキーを強く押しても、これ以上のヒントは表示されません。
これは、明示的なコンパイルを実行し、ガンコードを削除することで処理されます。
どうぞ
三項演算子
この記事では、三項演算子の問題も強調しています。 コードのコンパイル時間はコメントで見ることができます:
// Build time: 239.0ms let labelNames = type == 0 ? (1...5).map{type0ToString($0)} : (0...2).map{type1ToString($0)} // Build time: 16.9ms var labelNames: [String] if type == 0 { labelNames = (1...5).map{type0ToString($0)} } else { labelNames = (0...2).map{type1ToString($0)} }
ところで、SDKにはtype0ToStringのようなメソッドは見つかりませんでした。 単純化したバージョンに置き換えましたが、違いはありません。
let labelNames = type == 0 ? (1...5).map{String($0)} : (0...2).map{String($0)}
コンパイル時間 :260 ms。 これまでのところ、すべてが確認されています。
しかし、私には、三項演算子が不当に非難されているようです。 if-elseを使用せずに、式を別の式に分割してもう一度試してみましょう。
let first = (1...5).map{String($0)} let second = (0...2).map{String($0)} let labelNames = type == 0 ? first : second
コンパイル時間 :45ミリ秒
しかし、これは制限ではありません。 さらに簡素化:
let first = 4 let second = 5 let labelNames = type == 0 ? first : second
コンパイル時間 :7ミリ秒。
判定:三項演算子は正当化されます。
さらにいくつかの恩赦
操作ラウンド():
// Build time: 1433.7ms let expansion = a - b - c + round(d * 0.66) + e
コンパイル時間 :6ms
配列の追加:
// Build time Swift 2.2: 1250.3ms // Build time Swift 3.0: 92.7ms ArrayOfStuff + [Stuff]
コンパイル時間 :19ms
そして最も甘いもの:
let myCompany = [ "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"], ] ]
コンパイル時間 :86ミリ秒。 それは良かったかもしれませんが、少なくとも12時間ではありません。
これで、最初の部分を終了したいと思います。 その中で、オプションおよび三項演算子、配列の追加、およびいくつかの関数に関する神話を暴きました。 オートコンプリートがフリーズする理由の1つについて学習し、Swiftのコンパイルによって複雑な数式が最も遅くなることもわかりました。 お役に立てば幸いです。
第二部 。