🐕

Firebase iOS SDK 10.8.0でFirestoreのビルドが短くなったので試してみる

2023/04/12に公開

こんちは、あらさんです。ロックマンエグゼアドコレの発売日が近づいていてワクワクしている毎日です、発売日はお仕事をお休みしてネットバトラーになります。ぜひ一緒にやりましょう、Twitterで気軽なお誘いお待ちしております。

https://www.capcom-games.com/megaman/exe/ja-jp/

Firebase iOS SDK 10.8.0について

今回の話題にあげる内容は、ビルドをもっと高速化するためにバイナリターゲットとして配布してくれ、というIssueを解決した成果によるものです。当時SPMはまだ本格対応するには時期が早すぎる、必要な機能が揃っていないなどの理由で今まで見送られてきました。そして2023/04/11の週のリリースにより、ついにリリースされることになった待望の機能になります。

対象のIssue

https://github.com/firebase/firebase-ios-sdk/issues/6564

今回のリリースノート

https://firebase.google.com/support/release-notes/ios#cloud-firestore

さっそく試してみる

利用する環境はこちら。

- Mac mini(2023)
- M2 Pro (12コアCPU, 19コアGPU, 16コアNeural Engine)
- メモリ: 32GB
- ストレージ: 1TB

試すプロジェクトはXcode 14.3、新規プロジェクトでiOSアプリをSwiftUIとして選択したProjectからFirebase 10.8.0と10.7.0をインストールした2種類を用意しました。

まずはDependenciesに表示されている項目から比較します。パット見でわかるのはabseilのバージョンが上がり、中にはソースが表示されなくなっているようです。またBoringSSL-GRPCがまるごと依存から消えています。

まずはabseilの方からPackage.swiftを覗いてみると10.8.0でbinaryTargetに変更されたことが分かります。この方式で提供されると手元でのビルドをせずに済むため単純なビルド時間の低下につながるようです。またXcodeのClean Build Folderをした場合には今までの方式だとabseilのビルドまで消えてしまって無駄にビルド時間が伸びてしまう、といったことが発生していましたがbinaryTargetは消えないためビルドが効率よく働きます。

Package.swiftコードの差分
10.8.0-abseil
let package = Package(
  name: "abseil",
  products: [
    .library(name: "abseil", targets: ["abseil"])
  ],
  targets: [
    .binaryTarget(
      name: "abseil",
      url: "https://dl.google.com/firebase/ios/bin/abseil/1.2021110200.0/abseil.zip",
      checksum: "6776cf3f28e84965a2725861012f67e77dfa56f7ebf147f5671c26b1d9585440"
    )
  ]
)
10.7.0-abseil
let package = Package(
  name: "abseil",
  products: [
    .library(
      name: "abseil",
      targets: [
        "abseil",
      ]
    )
  ],

  targets: [
    .target(
      name: "abseil",
      path: ".",
      exclude: [
        // main functions
        "absl/hash/internal/print_hash_of.cc",
        "absl/random/internal/gaussian_distribution_gentables.cc",
      ],
      sources: [
        "absl/"
      ],
      publicHeadersPath: ".",
      cSettings: [
        .headerSearchPath("./"),
      ],
      linkerSettings: [
        .linkedFramework("CoreFoundation"),
      ]
    ),
    .testTarget(
      name: "build-test",
      dependencies: [
        "abseil",
      ],
      path: "SwiftPMTests/build-test"
    ),
  ],

  cxxLanguageStandard: CXXLanguageStandard.gnucxx14
)

さてBoringSSL-GRPCも消えたため何に置き換わったのか見ていきます。gRPCのバージョンが1.44.3-grpcから変わっているため見てみます。変更点をみるとbinaryに置き換わっているようです。このgrpc-xxxのPackage.swiftも見てみましょう。

10.8.0-firebase
.package(
  url: "https://github.com/google/grpc-binary.git",
  "1.44.0" ..< "1.45.0"
),
10.7.0-firebase
.package(
  name: "gRPC",
  url: "https://github.com/grpc/grpc-ios.git",
  "1.44.0-grpc" ..< "1.45.0-grpc"
),

gRPCのPackage.swiftのコードを見るとbinaryTargetに大きく置き換わっていますね。手元でのビルドをしなくてもよいように整えられていることが分かります。またgRPCがBoringSSL-GRPCの依存を持っていたが消えているため差分が生まれたようです。

Package.swiftコードの差分
10.8.0-grpc
let package = Package(
  name: "gRPC",
  products: [
    .library(
      name: "gRPC-Core",
      targets: ["gRPC-Core"]
    ),
    .library(
      name: "gRPC-C++",
      targets: ["gRPC-CXX-Target"]
    ),
  ],
  dependencies: [
    .package(
        url: "https://github.com/google/abseil-cpp-binary.git",
        "1.2021110200.0" ..< "1.2021110300.0"
    )
  ],
  targets: [
    .target(
      name: "gRPC-CXX-Target",
      dependencies: [
        .target(name: "gRPC-CXX-Wrapper")
      ],
      path: "SwiftPM-PlatformExclude/gRPC-CXX-Target"
    ),
    .target(
      name: "gRPC-CXX-Wrapper",
      dependencies: [
        .target(name: "gRPC-Core"),
        .target(name: "gRPC-C++"),
        .target(name: "BoringSSL-GRPC"),
        .product(name: "abseil", package: "abseil-cpp-binary"),
      ],
      path: "gRPC-CXX-Wrapper"
    ),
    .binaryTarget(
      name: "gRPC-Core",
      url: "https://dl.google.com/firebase/ios/bin/grpc/1.44.0/gRPC-Core.zip",
      checksum: "6c8ae417ea4caf6252c8c68ca7092cb11d12436501727a24a01a8371336c2bd0"
    ),
    .binaryTarget(
      name: "gRPC-C++",
      url: "https://dl.google.com/firebase/ios/bin/grpc/1.44.0/gRPC-C++.zip",
      checksum: "c1f167f54b4715d71b5e2c80476b7bf0b58cc8e21633a1f267df339e796d3cca"
    ),
    .binaryTarget(
        name: "BoringSSL-GRPC",
        url: "https://dl.google.com/firebase/ios/bin/grpc/1.44.0/BoringSSL-GRPC.zip",
        checksum: "7ba45f311a17a8b613f96316595f395c76eb7439117f958eba3735caa55e0fd7"
    )
  ]
)
10.7.0-grpc
let package = Package(
  name: "gRPC",
  products: [
    .library(
      name: "gRPC-Core",
      targets: [
        "gRPC-Core",
      ]
    ),
    .library(
      name: "gRPC-cpp",
      targets: [
        "gRPC-cpp",
      ]
    )
  ],

  dependencies: [
    .package(
      name: "abseil",
      url: "https://github.com/firebase/abseil-cpp-SwiftPM.git",
      "0.20220203.0"..<"0.20220204.0"
    ),
    .package(
      name: "BoringSSL-GRPC",
      url: "https://github.com/firebase/boringssl-SwiftPM.git",
      "0.9.0"..<"0.10.0"
    ),
  ],

  targets: [
    .target(
      name: "gRPC-Core",
      dependencies: [
        .product(name:"abseil", package: "abseil"),
        .product(name:"openssl_grpc", package: "BoringSSL-GRPC"),
      ],
      path: "native_src",
      exclude: [
        "src/core/ext/filters/load_reporting/",
        "src/core/ext/transport/cronet/",
        "src/core/ext/xds/google_mesh_ca_certificate_provider_factory.h",
        "src/core/ext/xds/google_mesh_ca_certificate_provider_factory.cc",
        "third_party/re2/re2/testing/",
        "third_party/re2/re2/fuzzing/",
        "third_party/re2/util/benchmark.cc",
        "third_party/re2/util/test.cc",
        "third_party/re2/util/fuzz.cc",
        "third_party/upb/upb/bindings/",
        "third_party/upb/upb/msg_test.cc",
        "src/core/lib/surface/init_unsecure.cc",
        "src/core/ext/filters/client_channel/lb_policy/grpclb/grpclb_channel.cc",
        "src/core/lib/security/authorization/authorization_policy_provider_null_vtable.cc",
      ],
      sources: [
        "src/core/ext/filters/",
        "src/core/ext/transport/",
        "src/core/ext/upb-generated/",
        "src/core/ext/upbdefs-generated/",
        "src/core/ext/xds/",
        "src/core/lib/",
        "src/core/plugin_registry/grpc_plugin_registry.cc",
        "src/core/tsi/",
        "third_party/re2/re2/",
        "third_party/re2/util/",
        "third_party/upb/upb/",
        "third_party/xxhash/xxhash.h",
      ],
      publicHeadersPath: "spm-core-include",
      cSettings: [
        .headerSearchPath("./"),
        .headerSearchPath("include/"),
        .headerSearchPath("third_party/re2/"),
        .headerSearchPath("third_party/upb/"),
        .headerSearchPath("third_party/xxhash/"),
        .headerSearchPath("src/core/ext/upb-generated/"),
        .headerSearchPath("src/core/ext/upbdefs-generated/"),
        .define("GRPC_ARES", to: "0"),
      ],
      linkerSettings: [
        .linkedFramework("CoreFoundation"),
        .linkedLibrary("z"),
      ]
    ),
    .target(
      name: "gRPC-cpp",
      dependencies: [
        .product(name:"abseil", package: "abseil"),
        "gRPC-Core",
      ],
      path: "native_src",
      exclude: [
        "src/cpp/client/cronet_credentials.cc",
        "src/cpp/client/channel_test_peer.cc",
        "src/cpp/common/alts_util.cc",
        "src/cpp/common/alts_context.cc",
        "src/cpp/common/insecure_create_auth_context.cc",
        "src/cpp/server/admin/",
        "src/cpp/server/channelz/",
        "src/cpp/server/csds/",
        "src/cpp/server/load_reporter/",
        "src/cpp/ext/",
        "src/cpp/util/core_stats.cc",
        "src/cpp/util/core_stats.h",
        "src/cpp/util/error_details.cc",
      ],
      sources: [
        "src/cpp/",
      ],
      publicHeadersPath: "spm-cpp-include",
      cSettings: [
        .headerSearchPath("./"),
        .headerSearchPath("include/"),
        .headerSearchPath("third_party/upb/"),
        .headerSearchPath("src/core/ext/upb-generated"),
      ]
    ),
    .testTarget(
      name: "build-test",
      dependencies: [
        "gRPC-cpp",
      ],
      path: "native_src/test/spm_build"
    ),
  ],
  cLanguageStandard: .gnu11,
  cxxLanguageStandard: .cxx11
)

いざビルド

厳密なビルド速度は求めません。数回試行した結果を記載するためより詳細なデータが欲しい場合には手元で試してください。プロジェクトは新規作成から何も手を加えず、Xcodeの設定からFirebaseFirestore, FirebaseFirestoreSwiftのみを含めたものとしています。また計測時間はXcodeのログからBuild succeeded xxxx/xx/xx xx:xx xx.x secondsとして記載されているところからそのまま持ってきています。厳密なFirestoreのみのビルド時間ではないことに注意してください。

計測方法はiPhone 14 Plusのシミュレータを対象にデバッグビルド -> Clean Build Folderを繰り返します。

Firebase 10.8.0 Firebase 10.7.0
13.5s 49.9s
4.1s 48.6s
4.2s 47.5s
4.2s 47.0s

考察

雑に試してみたところ初回のビルド時点で大幅にビルド時間が短くなっていることが分かりました。以前までのバージョンではClean Build Folderを繰り返すと都度Firestoreに関連する内容でビルド時間が伸びていたことを考えると嬉しい限りです。また2回目からのビルドを見てみると10.8.0では4秒台と驚異的なビルド時間になっています。詳しい理由は調べていないため分かりませんがClean Build Folderで消えない場所(DerivedDataかな?)に何かしらの成果物を残してくれたお陰で短くなっているように見えます。
詳しいことはわからなくてもFirebaseを素直にアップデートするだけでビルド時間が大幅に短くなるようなので利用者としては非常に嬉しいですね、CIに捧げるお金やビルド待ちの時間も大幅に圧縮できるかも!

Discussion