ちょっと話題の記事

#cmdevio2016 (レポート: B-3) Swiftで書かれたコードのパフォーマンス・チューニング

2016.02.21

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

本記事ではDevelopers.IO 2016 セッションB-3でRealm 岸川克己氏に発表いただきましたSwiftで書かれたコードのパフォーマンス・チューニングのレポートをお届けします。

はじめに

  • 今回しない最適化の話
    • アルゴリズム
    • メモリ
  • 言語機能をつかった最適化の話をします。

自己紹介

  • Realmで働いています。
  • 是非この後もRealmについて聞きに来てください。
  • SQLiteより高速で使いやすいです。

最適化の前に

  • 早すぎる最適化は諸悪の根源である(Knuth先生: Art of computer Programming の著者)

  • 最適化の前に計測する

  • 可読性とのトレードオフ
  • UITableViewとかでキャッシュをクリアするとかもあるある

  • ソフトウェア 80 / 20 ルール (20%の実行コードが80%の実行時間を占めている)

    • ありとあらゆるところを最適化しても効果は得られない
    • 可読性が落ちるだけということがありがちである
  • Realm以外にもアドバイザをしているが、まず最適化に関するPullReqの必要性の観点からレビューしている

計測する

  • 細かく計測してどこがボトルネックになっているかまずチェック

  • Instruments

  • Time Profiler

    • まずボトルネックを追うためにTimeProfilerにかける
    • スタックトレースがでてきてビジュアルに出してくれるため、どこがボトルネックになっているかわかる
    • だいたいはこれで済む

コードの性能を上げるには

  • 明示的な方法
    • だいたい可読性とトレードオフになる
    • 本当にそれがコストに見合っているか?
  • 暗黙的な方法
    • 言語の最適化を利用
    • コンパイラの設定を変える
    • 常に気をつけてやっていればいい類の最適化
    • 言語特有のため、Swiftの内部的な話には詳しくなっておもしろいが、それはそれでエンジニアとしてのバランスが悪くなる。

最適化を有効にする

  • Release構成でビルドする
    • やらなくてよかった最適化をしないためにも最初はRelease構成で
    • なにが行われるかというと、コンパイラ(Swiftc)の最適化オプションが変わる
    • Appleが出しているSwiftのブログにも書かれている
    • コンパイル時のみでなくリンク時最適化がかかる
    • Uncheckフラグはケース・バイ・ケース。不用意につけないほうが良い
    • 最初の一年はSwiftのRelease構成の挙動が不安定だったが、最近は聞かない

最適化を確認するには

  • Swiftのコンパイラオプションを確認
    • コマンドラインでやるのは大変
    • どちらかというとこれから話すことを理解&確認するためのコマンド
    • -emit-sil フラグはSwift中間言語を確認するためのフラグ

注意

  • これから話す最適化トピックの最後の方には私が確認していないものもあります。

final & private を使う

  • final class: クラスの継承不可
  • final var: override不可
  • final func: override不可
    • vTable(仮想テーブル)の検索が早くなるため、呼び出しが早くなる場合が多い
    • 動的ディスパッチが避けられる
  • private class: 他ファイルから使われない

  • private var: 他ファイルから使われない, 継承先から見えない
  • private func: 他ファイルから使われない, 継承先から見えない
    • privateは自動的にfinal扱いになる

ReferenceTypeよりValueTypeを使う

  • ValueTypeは

    • Immutable
    • 故にThreadSafe
    • メモリアロケーションのやり方が異なるため、パフォーマンスに影響する
    • できるだけValueTypeを使っていくことがSwiftらしいコード
  • x: Intを定数としてもつ struct vs class の場合...
    • structの方が早い
    • emit assemblyしてみると…
    • 代入コードがstructはシンプル
    • classはarcの制御コードが入る
  • 配列にReferenceTypeを格納するのを避ける
    • ReferenceTypeを配列に入れるときはContiguousArrayを使うことを検討する(たいていの場合、暗黙的にNSArrayが用いられるため)
    • Memory Allocationの問題とReferenceCountの問題、配列の個数の問題が積み重なってパフォーマンスに影響する
  • ContiguousArrayはドキュメントにもあまりかかれていないが、NSArrayよりも効率的

Objective-Cを避ける

  • 動的ディスパッチが発生する
    • コンパイラが最適化できない
    • なんでもかんでもNSObjectを継承しているから、最適化の観点から見ればおすすめできない
    • Objective-Cはどこまで行っても文字列
    • Objective-Cから使えるSwiftのコードも動的ディスパッチが用いるため、最適化の観点からは避けたほうが良い

遅延(Lazy)させる

  • 必要に応じてLazyな変数を用いる

    • Global Variable
    • Type Variable
      • 名前空間的に用いればキャッシュ的に使える
    • Lazy Property
      • Realmのインスタンス初期化を使うときに用いると起動時にマイグレーションで時間がかかる問題が解消される
    • Computed Property
  • Lazy collection
    • map, filter内のコードはアクセス時に実行される

参考資料

感想

Swiftの言語機能を用いたパフォーマンス・チューニングということでGithubのドキュメントを元に講演されていました。 Swiftのみならず、Objective-Cの深い箇所までの理解が求められる尖ったセッションで、個人的に非常に面白かったです!