Signposts と Instruments でパフォーマンスを計測する：基礎編

Signposts は Xcode 10 で導入されたパフォーマンスを計測するためのマーカーです。この記事では Signposts を実際に活用する人のために、Signposts でできることをまとめておきます。初心者用の Signpost の使い方についてはこちらの記事をご覧下さい。

Signposts と Instruments でパフォーマンスを計測する：入門編
https://qiita.com/hsawada/items/e027b85480c8778f154a

#処理の時間を測る

パフォーマンス計測用のマーカーなので、まずは処理の時間を測るのが基本です。

```swift
let logSomething = OSLog(subsystem: "com.example.your-app", category: "Something")

os_signpost(.begin, log: logSomething, name: "fetch something")
fetchSomething()
os_signpost(.end, log: logSomething, name: "fetch something")
```

ログのハンドルを取得して、計測したい箇所を `os_signpost()` の `.begin` と `.end` で囲みます。

#非同期処理の時間を測る

上記の `fetchSomething()` が非同期に何度も呼ばれると、 `.begin` と `.end` が対応しなくなり、正しく計測できなくなってしまいます。そういう時は `OSSignpostID()` を使って、ID を作成し、対応する `.begin` と `.end` に同じ ID を指定するようにします。

```swift
let spid = OSSignpostID(log: logSomething)

os_signpost(.begin, log: logSomething, name: "fetch something", signpostID: spid)
fetchSomething()
os_signpost(.end, log: logSomething, name: "fetch something", signpostID: spid)
```

ただ、これだと、 ID の管理が面倒になることが多いので、オブジェクトから ID を取得する方法を取るのが便利だと思います。

```swift
let spid = OSSignpostID(log: logSomething, object: element)
```

オブジェクトごとに ID が取得できるので、下記のような使い方ができます。

```swift
let logSomething = OSLog(subsystem: "com.example.your-app", category: "Something")

for element in panel.elements {
    let spid = OSSignpostID(log: logSomething, object: element)
    os_signpost(.begin, log: logSomething, name: "fetch something", signpostID: spid)
    fetchSomethingAsync() {
        os_signpost(.end, log: logSomething, name: "fetch something", signpostID: spid)
    }
}
```

#メタデータを記録する

時間を測ると同時にその時に必要なメタデータを記録することもできます。例えば、イメージのダウンロードあれば、そのイメージのサイズなどをログとして記録することができます。

```swift
os_signpost(.begin, log: logSomething, name: "fetch something", "%d %.1f", x1, y1)
```
フォーマットさえきちんと指定すれば、いろいろなタイプでたくさんの引数を指定することが可能です。
ここでは簡単な例を示すために、数値を並べていますが、実際に使用する時には、それぞれの数値の説明も含めて記録しておくべきです。後で分析する時に何の数値の羅列かを、コードと照らし合わせなければ、分からなくなってしまうからです。

#イベントを記録する

パフォーマンスを分析する時、処理にかかる時間ばかりでなく、何かのイベントを記録したいこともよくあります。そんな時には `os_signpost()` に `.event` を指定します。

```swift
os_signpost(.event, log: logSomething, name: "tap something")
```

タップやスワイプなどのユーザー操作や、ネットワークコネクション確立後のイメージのダウンロード開始など、いわゆる普通のログと同じように、パフォーマンス分析において、興味のあるイベントをこれで記録します。

#マーカーを無効にする

Signposts は非常に軽量で、アプリのリソースに影響はほとんど与えないそうです。しかしながら、限られた場所に大量にマーカーがあって、その影響が懸念される場合や、一部のマーカーを無効にして、分析しやすくしたい場合には、カテゴリーごとにマーカーを無効化できます。

マーカーのカテゴリーは、ログのハンドルを取得する際に指定しています。

```swift
let logSomething = OSLog(subsystem: "com.example.your-app", category: "Something")
```

このログのハンドルに対して、 `OSLog.disabled` をセットすると、このハンドルを使用するマーカーは全て動かなくなります。つまり、このカテゴリーのマーカーは全て動かなくなるわけです。

```swift
logSomething = .disabled
```

したがって、マーカーを入れていく際には、ログをどのようにカテゴリー分けするかについて、よく考慮しておいた方が良いと思います。パフォーマンスのマーカーを使い始めると、至る所に入れまくってしまい、いざ分析するときに注目したいデータが埋もれてしまうことがよくあるからです。適切にカテゴリー分けをしておけば、後々の管理がずっと楽になると思います。

#計測専用のコードを分離する

上記のマーカーの無効化に関係しますが、計測専用のコードを書く場合には、ログハンドルの `.signpostsEnabled` を使って、通常のコードから分離しておいた方がいいと思います。

```swift
if logSomething.signpostEnabled {
    fetchMetadata()
}
```

例えば、画像のファイルサイズの取得など、記録したいメタデータを取得するためだけにコードを書く場合には、このようにして通常コードと分離しておけば、そのカテゴリーが無効の時に、メタデータ取得のコードは実行されなくなります。

#まとめ

Signposts を使うと、処理の継続時間やイベントを記録でき、それと同時に任意のメタデータも記録できます。また、状況に応じて、カテゴリごとにマーカーや計測用のコードを無効化することもできます。

#参考文献

Measuring Performance Using Logging
https://developer.apple.com/videos/play/wwdc2018/405