迅速かつコンパイル時間

この投稿は、記事medium.com/@RobertGummesson/regarding-swift-build-time-optimizations-fc92cdd91e31 +小さな変更/追加に基づいています。 つまり 、テキストは3番目ではなく、私の顔からのものです。



ファイルを変更した後、または単にプロジェクトを再度開いた後は、誰もが状況に精通しています。













Objective-Cで書いたとき、特別なコンパイルの問題に気づかなかったと言わなければなりませんが、すべてが迅速な到来とともに変わりました。 私は、iOSプロジェクトを組み立てるためのCIサーバーを部分的に担当しました。 そのため、Swiftのプロジェクトは耐えられないほど遅くなりました。 開発者はまた、迅速なプロジェクトの全員のためにポッド(依存関係のココアポッド)をドラッグすることも好きです。 そのため、プロジェクト全体は文字通り数十のクラスで構成されていましたが、この混合物全体のコンパイルは数分続くことがありました。 まあ、それはそうでしたが、言語は新しく、コンパイル段階でObjC自体よりも多くのチェックがあり、それがより速く動作することを期待するのは愚かです(はい、swiftは強く型付けされた言語であり、すべてが明示的に宣言される必要があります(たとえば、Java)、すべてがそれほど厳密ではないObjCとは異なります)。 Swift開発者は、各リリースでx倍のコンパイル速度を約束します。 確かに、実際にはアセンブリ2.0と2.2のみがより速く動作し始め、現在はバージョン3.0(年末)になっています。



コンパイラーはコンパイラーですが、ほんの数行のコードでプロジェクトのビルド時間を劇的に延長できることがわかりました。 いくつかの例は上記の記事から取られたものであり、いくつかは自己記述です(著者を信じていなかったので、自分でチェックしたかったです)。 計測された時間



time swiftc -Onone file.swift
      
      







コンパイラが遅くなる可能性のあるもののリスト





Nil Coalescing Operatorの不注意な使用





 func defIfNil(string: String?) -> String { return string ?? "default value" }
      
      







演算子?? expands Optional 、何かある場合は値を返し、そうでない場合は??の後の式 。 これらの演算子のいくつかを連続して接続すると、コンパイル時間を桁違いに増やすことができます(私は間違いではありません、桁違いに:))。



 //     0,09  func fn() -> Int { let a: Int? = nil let b: Int? = nil let c: Int? = nil var res: Int = 999 if let a = a { res += a } if let b = b { res += b } if let c = c { res += c } return res }
      
      







 //   3,65  func fn() -> Int { let a: Int? = nil let b: Int? = nil let c: Int? = nil return 999 + (a ?? 0) + (b ?? 0) + (c ?? 0) }
      
      







はい、40回:)。 しかし、私の観察では、同じ式で3つ以上のこのような演算子を使用する場合にのみ問題が発生することが示されました。



 return 999 + (a ?? 0) + (b ?? 0)
      
      





大丈夫です。



配列結合





Rubyでスクリプトを作成し、読みやすさを損なうことなく、数行のコードで多数の操作を実行できる素晴らしい言語でチョップすることもあります。 私は特に配列を扱うのが好きです。 つまり、これは私にとって非常に一般的なルビーコードです。



 res = ar1.filter { ... } + ar2.map { ... } + ar3.flatMap { ... }
      
      







このスタイルで迅速に記述すると、コンパイル時間を非常に損なう可能性がありますが、言語構造によりこの方法で記述できます。 それで、誰もが今免疫について話しているように、したがって、可変配列を作成することは一般的ではなく、一般的に悪い調子、pffです。 しかし、このように書くとどうなりますか?



 //  0,15  func fn() -> [Int] { let ar1 = (1...50).map { $0 }.filter { $0 % 2 == 0 } let ar2 = [4, 8, 15, 16, 23, 42].map { $0 * 2 } var ar3 = (1..<20).map { $0 } ar3.appendContentsOf(ar1) ar3.appendContentsOf(ar2) return ar3 }
      
      







 //  2,86  func fn() -> [Int] { let ar1 = (1...50).map { $0 } let ar2 = [4, 8, 15, 16, 23, 42] return (1..<20).map { $0 } + ar1.filter { $0 % 2 == 0 } + ar2.map { $0 * 2 } }
      
      







違いはほぼ20倍です。 しかし、2番目のメソッドごとに配列を使用します。 しかし、1つの式で3つ以上の配列を要約すると、このコンパイラーの動作が得られたことに注目する価値があります。



 return ar1.filter { $0 % 2 == 0 } + ar2.map { $0 * 2 }
      
      





すでに約



三項演算子





元の投稿の著者は、複雑な式を三項演算子と一緒に使用してはならないことを示す例を示しています。



 // Build time: 0,24  let labelNames = type == 0 ? (1...5).map{type0ToString($0)} : (0...2).map{type1ToString($0)} // Build time: 0,017  var labelNames: [String] if type == 0 { labelNames = (1...5).map{type0ToString($0)} } else { labelNames = (0...2).map{type1ToString($0)} }
      
      







過剰なカースト





ここでは、著者は単にCGFloatの余分なカーストを削除し、ファイルのコンパイルを大幅に高速化しました。



 // Build time: 3,43  return CGFloat(M_PI) * (CGFloat((hour + hourDelta + CGFloat(minute + minuteDelta) / 60) * 5) - 15) * unit / 180 // Build time: 0,003  return CGFloat(M_PI) * ((hour + hourDelta + (minute + minuteDelta) / 60) * 5 - 15) * unit / 180
      
      







複合式





ここで、著者とは、ローカル変数、クラス変数、およびクラスインスタンスと関数の混合物を意味します。



 // Build time: 1,43  let expansion = a - b - c + round(d * 0.66) + e // Build time: 0,035  let expansion = a - b - c + d * 0.66 + e
      
      







確かに、元の投稿の著者はその理由を理解していませんでした。








一般に、複雑な機能/言語構成を使用することはクールですが、このすべての構文糖がコンパイル時間とランタイム自体の両方に大きく影響する可能性があることを覚えておく価値があります。 彼らは、アンドロイドの下の岩に書くと、このすべての構文糖のために、メソッドの数が非常にすぐに制限され、余分な体の動きに頼る必要があると言います。



追伸



迅速な言語の開発者の1人が、この投稿に対して、swift 3でこの方向に多くの改善が行われたと元の著者に小さな回答をしました。




All Articles