概要
kago-utilsというPython製の麻雀OSSを作っている。
将来的にはこいつを拡張して強化学習を行うことが目標だ。
強化学習をするとなると速度は命だ。
そこでpytest-benchmarkを使って逐一ベンチマークを行い、その結果をGitHub Pagesにデプロイし、パフォーマンスを改悪するようなコミットをしてしまっていないかどうかを可視化することにした。
最終的には以下のようなGitHub Pagesができる。
基本的にはgithub-action-benchmark(GitHub)に書いてある通りにやればよいのだが、意外と行間を補完する必要があって手間取ったので、この記事にまとめておく。
サンプルプロジェクト
というわけで、kago-utilsにpytest-benchmarkを導入して上手くいったのだが、色々とコミットがごちゃごちゃしてしまった。
そこで今回は新しくこの記事用のリポジトリを作成したので、説明ではこちらを使う。
リポジトリは以下。
pytest-benchmark-with-github-actions-example
GitHub Pages用のブランチを作成
とりあえずコードを書く前にGitHub Pages用のブランチを作成しておく。
今回はベンチマークの結果を保存するだけなので空のブランチを作成する。
空のブランチを作成する方法が以下。
git switch --orphan gh-pages
git commit --allow-empty -m "Initial empty commit for gh-pages branch"
git push origin gh-pages
git switch main
git switch
やgit checkout
で新しいブランチを作成する際に--orphan
オプションをつけることで空のブランチを作成できる。
通常のコミットは何らかの変更点が必要だが、--allow-empty
オプションをつけることで、変更点なしの空のコミットを作成できる。これは今回四苦八苦する中で初めて知った。
あとはプッシュして終了。
これ以降は手動でgh-pages
ブランチに変更を加えることはないので、うっかりgh-pages
ブランチで作業をしてしまわないようにgit switch main
でmain
ブランチに戻っておく。
GitHubの管理画面でGitHub Pages用の設定を行う
Settings > PagesのBranchにgh-pages
を選択する。
自分の場合は自動でgh-pages
が選択されていたが念の為。
mainブランチでの作業
ディレクトリ構成
├── Pipfile
├── Pipfile.lock
├── README.md
├── src
│ ├── __init__.py
│ └── prime.py
└── tests
├── __init__.py
└── test_prime.py
Pipfile
の作成
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
[dev-packages]
pytest = "*"
pytest-benchmark = "*"
[requires]
python_version = "3.12"
[scripts]
test = "python -m pytest --benchmark-skip"
benchmark = "python -m pytest --benchmark-only --benchmark-json output.json"
自分はPipenvに慣れているのでPipenvを使っているが、pipやpoetryやuvなど各々の環境に併せて適宜読み替えてほしい。
今回はテストツールとしてdev-packagesにpytestとpytest-benchmarkだけを追加している。
ベンチマークは意図的に負荷のかかる処理を書くこともあるので、通常のテストでは実行したくない。
そこでpipenv run test
ではベンチマークを除くテストを、pipenv run benchmark
ではベンチマークを含むテストを実行するように棲み分けている。
ベンチマーク時には--benchmark-only
オプションをつけてoutput.json
を出力しているが、このファイルはGitHub Pagesにデプロイする際に利用される。
src/prime.py
の作成
def is_prime(n):
factors = []
for i in range(1, n + 1):
if n % i == 0:
factors.append(i)
return len(factors) == 2
素数判定の素朴な関数を実装している。
遅い。
tests/test_prime.py
の作成
from src.prime import is_prime
def test_is_prime():
assert is_prime(1) is False
assert is_prime(2) is True
assert is_prime(3) is True
assert is_prime(4) is False
assert is_prime(5) is True
assert is_prime(6) is False
assert is_prime(7) is True
assert is_prime(8) is False
assert is_prime(9) is False
assert is_prime(10) is False
assert is_prime(1009) is True
assert is_prime(1011) is False
assert is_prime(1013) is True
assert is_prime(1015) is False
def test_is_prime_benchmark(benchmark):
def benchmark_is_prime():
is_prime(10000019)
benchmark(benchmark_is_prime)
test_is_prime
では通常のテストを、test_is_prime_benchmark
ではベンチマークを行っている。
ファイルサイズが大きくなりそうだったら通常のテストとベンチマークとでファイルを分けるのもありかもしてない。今回は面倒だったので一緒にしている。
ベンチマークしたいメソッドは引数にbenchmark
を指定すればpytest-benchmark
がよしなにベンチマークしてくれるのだが、benchmark
という文言がimport
なども無く突然湧いて出てくるのが正直トリッキーだなと思ってしまう。
workflows/benchmark.yml
の作成
name: Benchmark
on:
push:
branches:
- main
permissions:
deployments: write
contents: write
checks: write
pull-requests: write
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install pipenv
run: |
python -m pip install --upgrade pip
python -m pip install pipenv
- name: Install dependencies
run: python -m pipenv install --dev
- name: Run benchmark
run: python -m pipenv run benchmark
- name: Store benchmark result
uses: benchmark-action/github-action-benchmark@v1
with:
tool: 'pytest'
output-file-path: output.json
github-token: ${{ secrets.GITHUB_TOKEN }}
auto-push: true
このyamlファイルでGitHub Actionsの制御を行う。
on:
push:
branches:
- main
と書くことで、main
ブランチへのプッシュをトリガーとしてGitHub Actionsが実行されるようになる。
permissions:
deployments: write
contents: write
checks: write
pull-requests: write
と書くことで、GitHub Pagesにデプロイするための権限の設定をしている。
- name: Store benchmark result
uses: benchmark-action/github-action-benchmark@v1
with:
tool: 'pytest'
output-file-path: output.json
github-token: ${{ secrets.GITHUB_TOKEN }}
auto-push: true
と書くことで、前段のpipenv run benchmark
で出力したoutput.json
をHTMLファイルに変換して、GitHub Pagesにデプロイしてくれる。
変換やデプロイなどの処理はgithub-action-benchmarkというactionsが行ってくれる。
デプロイにはGITHUB_TOKEN
が必要だが、${{ secrets.GITHUB_TOKEN }}
と書くことでGitHub Actionsが自動で値を代入してくれる。
基本的にシークレットはGitHubの管理画面で環境変数のような感じで自分で設定するものだが、secrets.GITHUB_TOKEN
はデフォルトで使用できるので管理画面での設定は必要ない。
コードをpushする
git push origin main
プッシュしたらGitHubのActionsタブを開いてGitHub Actionsの様子を見守る。
GitHub Pagesの確認
GitHub Actionsが成功したら、GitHub PagesのURLを開いてみる。
まず、画像の赤丸で囲まれた部分を押す。
つぎに、画像の赤丸で囲まれた部分を押す。
そして出てきたページは404エラーになっているはずなので、URLの末尾に/dev/bench
を追加する。
これでベンチマークを確認できる。
1秒で1.9イテレーションくらい回せているらしい。
src/prime.py
の修正
ChatGPTにいい感じに速度を改善してもらった。
def is_prime(n):
if n < 2:
return False
if n == 2:
return True
if n % 2 == 0:
return False
for i in range(3, int(n**0.5) + 1, 2):
if n % i == 0:
return False
return True
これをプッシュしてみる。
12000倍くらい速くなった。やったぜ。
コメント