Closed2

swift5.8からのcross-module optimization

morninmornin

@inlineableについて

これをつけると関数をインライン展開してくれる。C++だとコンパイラが優秀なのでほとんど考える必要がなくなったがSwiftだとどうなるのか?

https://swiftrocks.com/the-forbidden-inline-attribute-in-swift

さて、あなたはこう疑問に思うかもしれません:一体いつこれを行うのが良いアイデアなのでしょうか?
Apple のエンジニアによると、答えは基本的にありません。この属性はパブリックに使用でき、Swift ソース コードで広く使用されていますが、パブリックでの使用は正式にサポートされていません。ジョーダン・ローズの言葉を借りれば、アンダースコアには理由があるのです。これを使用すると、既知および未知の問題が多数発生する可能性があります。

使用に関して、この記事ではどういう場合に使用するかについて明確な答えがなく、一応難読化の観点から説明されている。

次にもう一個記事が書かれていて、こっちはクロスモジュールの話をしている

https://swiftrocks.com/understanding-inlinable-in-swift

コンパイラーの最適化は、コンパイラーがコンパイル対象の全体像を把握しているために行われますが、フレームワークを構築しているとき、コンパイラーはインポーターがどのようにフレームワークを実行しているかを知ることができない可能性があります。その結果、フレームワークの内部コードは最適化されますが、パブリック インターフェイスはそのまま残る可能性が高くなります。

なるほど。

コンパイラの動き

コンパイラーはインポーターがどのようにフレームワークを実行しているかを知ることができない可能性があります。その結果、フレームワークの内部コードは最適化されますが、パブリック インターフェイスはそのまま残る可能性が高くなります。

この部分にまだ疑問が残るので、少しコンパイラがどういう動きをするのか調べてみる。

単純にはこう

詳細見ると...こう!

出典は以下↓

https://qiita.com/rintaro/items/3ad640e3938207218c20

さてしばらく単一ファイルの話を見ていく。

https://www.swift.org/blog/whole-module-optimizations/

単一ファイルのコンパイルでは、コンパイラーの最適化の範囲は単一ファイルのみです。これにより、関数のインライン化や汎用特殊化などの関数間の最適化が、同じファイル内で呼び出され定義される関数に制限されます。

ほーほー

main.swiftとContainer.swiftを考える。

func add (c1: Container<Int>, c2: Container<Int>) -> Int {
  return c1.getElement() + c2.getElement()
}
struct Container<T> {
  var element: T

  func getElement() -> T {
    return element
  }
}

そうするとこう

コンパイラーが main.swift を最適化するとき、それがどのようにgetElement実装されるかはわかりません。ただそれが存在することを知っているだけです。したがって、コンパイラは への呼び出しを生成しますgetElement。

実装の詳細は知らんけどシグネチャ(Witness Tableに入っている?)は知っている。

コンパイラが utils.swift を最適化する場合、どの具体的な型に対して関数が呼び出されるのかはわかりません。したがって、生成できるのは関数の汎用バージョンのみであり、具体的な型に特化したコードよりもはるかに遅いです。

Genericsなので、Intかもしれないし大きなオブジェクトかもしれないし分からないので最適化できない...

Whole Module Optimaization

そんなあなたにWhole Module Optimaization

今回の場合、コンパイラが自動で判断して

これがこうして

struct Container {
  var element: Int

  func getElement() -> Int {
    return element
  }
}

こうなる!!

func add (c1: Container<Int>, c2: Container<Int>) -> Int {
  return c1.element + c2.element
}

やったぜ。

関数の特殊化とファイル全体のインライン化は、コンパイラーがモジュール全体の最適化で実行できる最適化の例にすぎません。コンパイラーが関数をインライン化しないと決定した場合でも、コンパイラーが関数の実装を確認できれば非常に役立ちます。たとえば、参照カウント操作に関する動作を推論できます。この知識を利用して、コンパイラは関数呼び出しの周りの冗長な参照カウント操作を削除できます。

Whole ModuleはOK。

Cross Moduleは?

Cross Module Optimization

https://forums.swift.org/t/brave-new-world-best-practices-for-cross-module-optimization/66869

Swift5.8からデフォルトで有効になる。けど、インライン化の基準はヒューリスティックに決まる。

| can you give a brief overview of what those heuristics are?

ヒューリスティックにとは具体的に...?

It's mainly based on the function size.

わからん。関数のサイズとかだ。

The critical problem with CMO is code size. Therefore the CMO which is enabled by default is much more conservative than the "aggressive" CMO, which must be explicitly enabled with -cross-module-optimization.

CMOは積極モードと消極モードがあって、デフォルトで有効になるのは消極モード。
コンパイラオプションを明示すると積極モードになるのでそっちだとインライン化もしてくれそう

なお、これまではパフォーマンス低下が懸念されていなかったので使わないのが了解事項だったらしい↓。

https://forums.swift.org/t/what-are-the-tradeoffs-of-cross-module-optimization/45585/13

結局、5.8でも消極モードでインライン化されるかどうかはわからないので、@inlibealeはつけたほうがいいのかしら。

(TCAのslackで軽く聞いてみたけどどうだろう...誰か応答してくれるといいな)

その他参考資料

調べているうちに最適化周りの記事をたくさん知れたのでよかった。
またまとめよう。

https://speakerdeck.com/kateinoigakukun/swiftfalserinkushi-zui-shi-hua-puroziekuto?slide=4

https://speakerdeck.com/kateinoigakukun/optimizing-your-swift-code?slide=2

morninmornin

どれだけの効果があるのかベンチマークをとってみることにした。

このスクラップは2023/09/13にクローズされました