GitHub Actions + Googleスプレッドシートでベンチマークを管理する

# 作ったもの
自分がメンテナンスしている[jackson-module-kotlin](https://github.com/FasterXML/jackson-module-kotlin)と、その実験機能を試験するためのプロジェクトである[jackson-module-kogera](https://github.com/ProjectMapK/jackson-module-kogera)のベンチマークプロジェクトを作成しました。

https://github.com/ProjectMapK/kogera-benchmark

このプロジェクトでは、`GitHub Actions`によるベンチマーク実行・スコア更新と、添付したようなスコア比較グラフの生成・反映を自動で行えます。
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280230/9392b60a-5d35-85a6-2425-04c2cdfb3f28.png)

# 背景
[jackson-module-kogera](https://github.com/ProjectMapK/jackson-module-kogera)は高性能さを謳っており、性能改善対応もしばしば行なっています。
また、それらの成果は[jackson-module-kotlin](https://github.com/FasterXML/jackson-module-kotlin)にも反映されています。

このような改善の結果を示すためにはベンチマークを行うことが重要です。
そのため、[kogera-benchmark](https://github.com/ProjectMapK/kogera-benchmark)プロジェクトにて様々な`JMH`ベンチマークを実装し、ローカルで計測していました。

一方、ベンチマーク項目を充実した結果、以下のような問題が生じました。

- 最低限の精度[^seido]を求めると完全実行に2時間程かかり、その間作業できなくなる
- 普段使いの`Windows`環境で実行しているため、冷却やバックグラウンドタスクの影響を受ける
- `README`への掲出に整理・抜粋が必要で、更新作業が大変
- 比較用のグラフを追加したかったが、素直にやると更新作業が更に大変になる
- 絶対スコアの変動を気にするとPCを買い替えられない

[^seido]: `JMH`では`fork`、`batchSize`、`iterations`などのパラメータをチューニングできます。これらの値は増やした方がベンチマークスコアの安定につながるものの、実行時間も増加するため、時間と精度のバランスを調整する必要があります。

そこで、ベンチマーク実行と結果管理をより簡単化した上で、結果表示用のグラフまで整備することを試みました。

# 構成
最終的に以下の構成に落ち着きました。

1. ベンチマークを`GitHub Actions`で実行し、結果を`CSV`形式で`GitHub`にコミット
2. 1によってスプレッドシートに更新が伝播
3. 2の反映が`GitHub`の`README`に掲出しているグラフにも伝播

## GitHub Actionsでのベンチマーク実行について
`GitHub`でリポジトリを管理している場合、`GitHub Actions`は手軽さの点で最高です。
一方、実行毎に常に全く同じマシンが使われ続けるとは限らないなど、継続的に絶対スコアを比較し続ける用途に用いることはできません。
ただし、同じマシンで直列に実行した上で、相対的な値を比較することは可能です。

この辺りに関しては、実際にやっている例を幾つか見つけることができました。

https://labs.quansight.org/blog/2021/08/github-actions-benchmarks

ローカルでの実行と比較して問題点も有りますが、いくつか実施例があり、実際に何度か実行した限りスコアのブレも許容できるように見えたため、`GitHub Actions`への移行を決めました。

### 実際のワークフロー
執筆時点でのワークフローファイルは以下の通りです。
内容は[melix/jmh-gradle-plugin](https://github.com/melix/jmh-gradle-plugin)で個別のベンチマークを直列に実行し、結果を[stefanzweifel/git-auto-commit-action](https://github.com/stefanzweifel/git-auto-commit-action)でコミットしているだけなので、解説は割愛します。

https://github.com/ProjectMapK/kogera-benchmark/blob/3b3e2f8e8ec4d72ab04ae69635a6f1f4d1f5edad/.github/workflows/benchmark.yml

## スプレッドシートでのグラフ管理について
グラフ生成に当たっては以下を実現したいと考えていました。

- `JMH`によるベンチマーク結果の`CSV`から生成できる
- `CSV`ファイルをコミットするだけでグラフを更新できる
- `Markdown`(`README`)に埋め込めば`GitHub`でプレビューできる

管理にスプレッドシートを選んだのは、操作が容易で、日頃から自分が使っているツールだったためです。
当初は[lets-plot-kotlin](https://github.com/JetBrains/lets-plot-kotlin)を使って生成することも考えましたが、習熟が容易ではなかったため見送りました。

### データの読み込みについて
スプレッドシートでは、`IMPORTDATA`関数によって`CSV`をはじめとしたデータをURLから読み込むことができます。
このデータはアクセスのたびに自動で更新が反映されます。

これを用いて`GitHub`にコミットしたベンチマーク結果を読み込み、加工することで、グラフの生成が可能になります。

### グラフの公開について
スプレッドシートには、グラフの公開機能が有ります。

https://support.google.com/docs/answer/1047436?hl=ja

型式に画像を選択して公開すれば、通常の画像と変わりなく`Markdown`に埋め込むことが可能です。
また、「変更が加えられたときに自動的に再公開する」を有効にすることで、同一のURLのまま更新を反映することが可能です[^gazou]。
![スクリーンショット 2023-08-03 3.31.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280230/f19174db-56c5-5611-7c2d-c2e7e66d44e5.png)

[^gazou]: 記事の冒頭に貼っている画像もこの形式で埋め込んで ~~いるため、Qiita側が対応していれば自動更新されます。~~ みましたが、Qiita側は非対応だったようで更新されませんでした。

この画像はキャッシュされるため、更新によって即時で反映されるとは限らない点に注意が必要です。
確認し切れている訳ではありませんが、スプレッドシートによって公開される画像には`Cache-Control: private, max-age=300`が設定されており、`GitHub`側はこの設定を尊重しているらしいため、最大でも5分程度で反映されると思われます。

https://github.com/orgs/community/discussions/22283

### 実際のシート
実際に利用しているシートは以下に公開しています。

https://drive.google.com/drive/folders/1mNa-bPybvhNTkGX7qkOfii_iMOI_gODG

# 今後の展望
以下は既に実現した状態になっています。

https://github.com/ProjectMapK/kogera-benchmark

---

一旦作ってみましたが、以下の点でまだ不満が有る状況です。

- 比較グラフを作成する上で、項目のフィルタリングといったスプシ上での加工が必要
- 直列実行しているせいで実行に時間がかかる
- 今の形式では変更前後での比較ができない

よって、時間が有るときに以下のような改善を行うつもりです。

- 比較内容毎のベンチマークグループ細分化・グラフ生成の簡易化
- 細分化したベンチマークの並列実行
- 比較対象設定の自由度向上

# 宣伝
開発の励みになりますので、リポジトリへのスターなどをお待ちしております。

https://github.com/ProjectMapK/jackson-module-kogera

`jackson-module-kotlin`へのスターもお待ちしています。

https://github.com/FasterXML/jackson-module-kotlin