【競プロ】ライブラリの verify を GitHub Actions で並列に走らせたい (oj-verify)

> この記事は 競プロ [Advent Calendar 2023](https://adventar.org/calendars/8745) の 17 日目の記事として書かれました。[^first-article]

> [Qiita全国学生対抗戦 Advent Calendar 2023](https://qiita.com/advent-calendar/2023/student) なるものもあるらしいので、ついでにそちらにも紐づけています。[^advent-calendar]
[^advent-calendar]: 特にダメという文言は見当たりませんでしたが、もし見落としていたら教えてください。

[^first-article]: 実は初投稿です、読みづらいかもしれません。赦してください。
記事の内容は表記に関するフィードバックをお待ちしております。

# 目次

- [はじめに](#はじめに)
- [本題](#本題)
    -  [0. 準備](#0-準備)
        - [0.0. oj-verify を GitHub Actions 上で動かす](#00-oj-verify-を-github-actions-上で動かす)
        - [0.1. テスト時に AC Library を自動で clone してもらう](#01-テスト時に-ac-library-を自動で-clone-してもらう)
    -  [1. 並列化](#1-並列化)
        - [1.0. job の並列化](#10-job-の並列化)
        - [1.1 プロセスの並列化](#11-プロセスの並列化)
    -  [2. テストケースをキャッシュする](#2-テストケースをキャッシュする)
    -  [3. もう少しだけ使いやすく](#3-もう少しだけ使いやすく)
    -  [4. 改善案](#4-改善案)
- [おわりに](#おわりに)

# はじめに
Verification[^verify]、してますか。私はしています。

[^verify]: verify とか verification とか呼んでいるのは大体競プロerな気がします。  
業プロだとテストとか言いそうですが、筆者は実務経験が無いのでわかりません。詳しい人教えてください。

ライブラリが充実してくると、それに要する手間もばかになりません。 

競プロer御用達の `oj-verify` も、結果をキャッシュしたりして高速化する工夫はなされていますが、それでも多くのファイルが依存しているヘッダ[^deep-dependence]に修正を加えた場合などにはかなりの時間がかかってしまいます。これは嬉しくないです。[^include-oj-verify]

[^deep-dependence]: 筆者の場合は `internal/type_traits.hpp` などがあります。その手のヤツです。
[^include-oj-verify]: 後述しますが、`oj-verify` は内部的に再利用します。互換性も保たれます。

そうですね、並列化ですね。
並列化しましょう。

## 想定している読者
- ライブラリ盆栽系競技プログラマの皆様
- `oj-verify` を GitHub Actions 上で動かそうとしたが、公式のドキュメント通りに操作しても謎のエラーで行き詰ってしまった人[^stucked]
- 並列処理を眺めてムフフしたい人[^mufufu]
- 自分のライブラリが [`AC Libray`](https://github.com/atcoder/ac-library) に依存しているが、そのコピーを自分のリポジトリに置きたくない人

[^stucked]: 私のことです。
[^mufufu]: 私です。

## 想定されたい筆者
- 学生[^strudent] (業プロ未経験[^biginner])

[^strudent]: 厳密には生徒が正しいかもしれません。
[^biginner]: やさしくしてください。

GitHub Actions も、これが作りたいがためにお勉強(ドキュメント漁り)をしました。  
なので「もっといい書き方があるよ！」みたいなことはたくさんあると思います。コメント等で教えてください。


## この記事のゴール

こういうものを作ります：
![product image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1089746/f2bfff37-7620-c7a0-400a-e11d6ac017a4.png)

ライブラリの verification を [GitHub Actions](https://github.co.jp/features/actions) 上で並列実行して、テストの時短を目指します。

ゼロから実装するのは大変なので、今回は [online-judge-tools/verification-helper](https://github.com/online-judge-tools/verification-helper/tree/master) (`oj-verify`) を内部的に呼び出すことにしました。
互換性が保たれるので、かえって嬉しいかもしれません。

:::note warn
この記事で提示している多くのソースコードは、**verify 対象のライブラリが C++ で記述されている** ことを前提として書かれています。  
C++ 以外の言語との互換性が失われる箇所については別途対応方法を簡単に記載しますが、基本的に対応例等を示すことはありません。ご了承ください。
:::

:::note warn
**Public リポジトリ** での使用が前提です。
課金額を最小化するような調整はしていません。  
無料で使える環境でのご利用を推奨します。
:::

:::note warn
GitHub Actions のランナーは **`ubuntu-22.04` のみ** を扱います。
`ubuntu-20.04` は動作するかもしれませんが、`macos-` 系 や `windows-` 系 のランナーで動かしたい人は頑張ってください。
普段 [WSL](https://learn.microsoft.com/ja-jp/windows/wsl/install) とかを使っている人は気にしなくて大丈夫だと思います。

:::

## 前提知識
基本的には以下のことが分かれば十分です：
- Git の基本的な操作


ただし GitHub Actions やシェルについて深くは解説しません。   
やってることを理解したりしたい人は次のことも分かる[^wakaru]と嬉しいかもです：
- GitHub Actions の仕様について
- Linux のシェルについて

[^wakaru]: 筆者は分かってません。

# 本題

[サンプル](https://github.com/uni-kakurenbo/parallel-verify-sample/tree/main) を用意しました。
ステップごとに該当するコミットへのリンクを示すので、適宜参考にしてください。

ところで、完成品だけが欲しい人は上述のサンプルをパクってくださればよろしいかと思います。

## 0. 準備

ライブラリをローカルのみに保存している人は適当にリポジトリを作成して push しておいてください。  

:::note
`oj-verify` がローカルで動作することを確認しておくのがいいと思います。[^oj-verify-local]
あとでもう一度述べますが、[ここ](https://online-judge-tools.github.io/verification-helper/document.ja.html)を読んでコンパイラのオプションの設定もしてあげてください。

:::

[^oj-verify-local]: 基本的には [公式ドキュメント](https://github.com/online-judge-tools/verification-helper/blob/master/README.ja.md)の記載通りで大丈夫なはずです。  

<br/>

**[ここまでのサンプル](https://github.com/uni-kakurenbo/parallel-verify-sample/tree/0c97969815bbb61d23425b5f15713e790f093961)**

---

### 0.0. `oj-verify` を GitHub Actions 上で動かす

筆者はまずここで沼りました。
`oj-verify` 側のドキュメントの整備が追いついていないのか、書いてある通りにしても動かないので、まずそれを修正します。

#### 0.0.0. ドキュメントに従う

[ここ](https://github.com/online-judge-tools/verification-helper/blob/master/README.ja.md#%E7%AB%B6%E3%83%97%E3%83%AD%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AE%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%81%AB%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B)のリンク先の「verify を自動でしてくれるように設定するには」を読んで設定してください。
何も起こらなければ正常です。(2023/12/16 現在)

:::note info
[ドキュメントのリンク先](https://online-judge-tools.github.io/verification-helper/installer.ja.html) にある「競プロライブラリの GitHub のレポジトリの URL」というのは `https://github.com/{{user-id}}/{{repository-name}}/` までの部分を指していると思われます。
末尾に `tree/{{branch-name}}` や `.git` などが付随していると 404 が出たりするので、そうなってしまった人は確認してください。
:::

<br/>

**[ここまでのサンプル](https://github.com/uni-kakurenbo/parallel-verify-sample/tree/12547b1f16600659bdeee45e689c046986420d19)**

---

#### 0.0.1. `verify.yml` を修正する

`./.github/workflows/` 下にあります。

##### 0.0.1.0. 不必要な依存関係のインストールを省く

 `required only if....` とのコメントが付いている [step](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idsteps) を削除しましょう。[^remove-install-deps]

```diff_yaml:verify.yml
-    # required only if you want to verify Java code
-    - name: Install dependencies (Java)
-      uses: actions/setup-java@v3
-      with:
-        distribution: temurin
-        java-version: '11'
- 
     #(他も同様)
```
 
[^remove-install-deps]: 先述した通り、この記事は C++ のみを動かす前提です。ほかの言語でも使いたい人は、その言語にあたるものを残しておいてください。


##### 0.0.1.1. `oj-verify` を正しくインストールする
###### 0.0.1.1.0. インストールコマンドを修正する
```diff_yaml:verify.yml
     - name: Install dependencies
-      run: pip3 install -U git+https://github.com/${{ github.repository }}.git@master
+      run: pip3 install -U online-judge-verify-helper
```



GitHub Actions の [`github.repository`](https://docs.github.com/en/actions/learn-github-actions/contexts#github-context) context はそのワークフローが置かれているリポジトリを指しますが、恐らく意図されているのは `oj-verify` のあるリポジトリです。[^oj-verify-install-commit]

[^oj-verify-install-commit]: 動くことが確認できたあとは `online-judge-verify-helper` の代わりにその時点での適当な commit をインストールするように書き換えると、`oj-verify` の更新が原因で急に壊れたりする心配がなくて安心かもです。

###### 0.0.1.1.1. Python のバージョンを指定する
```diff_yaml:verify.yml
     - name: Set up Python
       uses: actions/setup-python@v4
+      with:
+        python-version: 3.12.0
```

なくても動きますが、ずっと警告が表示され続けるので適当なバージョンを指定あげてしてください。  

##### 0.0.1.2. `actions/checkout@v3` に `fetch-depth: 0` を渡す

重要です。

```diff_yaml:verify.yml
     steps:
     - uses: actions/checkout@v3
+      with:
+        fetch-depth: 0
```
[`actions/checkout`](https://github.com/actions/checkout) の更新により `v2` 以降で必要になりました。[^fetch-depth:0]

[^fetch-depth:0]: これを指定しないと先頭の commit だけが取られます。

##### 0.0.1.3 `on.push.branches` を正しく指定する

```diff_yaml:verify.yml
 on:
   push:
     branches:
-    - master
+    - {{your-default-baranch-name}}
   pull_request:

```

`{{your-default-baranch-name}}` は自分のリポジトリの default branch の名前[^default-branch-name]で置換してください。
[^default-branch-name]: 現在は大抵の人が `main` だと思います。

default branch が `master` の人は何もしなくていいです。

ここまでやると、default branch への push や pull request があった際に自動的に `verify` workflow がトリガされるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1089746/b4418915-1e5a-0b77-64d4-a27600e3660e.png)

#### 0.0.2. コンパイラのオプションを設定する
[ここ](https://online-judge-tools.github.io/verification-helper/document.ja.html)の「C++ の設定」にしたがって、正しいコンパイラとオプションを指定してあげてください。
[はじめ](#0-準備)にやった人はしなくて大丈夫です。

<br/>

[**ここまでのサンプル**](https://github.com/uni-kakurenbo/parallel-verify-sample/tree/9a52dfbb396823366ce98339c78e1710c47592f6)

---

#### 0.0.3. 権限を正しく設定する

#### 0.0.3.0. Workflow permissions を修正する
Settubgs > Actions > General > Actions permissions > Workflow permissions
と進み、`Read and write permissions` を指定し、保存します。[^workflow-permissions]
[^workflow-permissions]: [権限を job 単位で指定する方法](https://docs.github.com/en/actions/security-guides/automatic-token-authentication)もあります。


![action permissions setting](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1089746/51d7e9d5-ef91-40bd-0158-0fc1fc227192.png)
![workflow permissions setting](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1089746/40d00326-c1b5-7bec-a0cf-84ba2bc8d050.png)


##### 0.0.3.1. `GH_PAT` secret を設定する
[ここ](https://online-judge-tools.github.io/verification-helper/installer.ja.html)の「ドキュメントが自動生成されるように設定するには」に従ってください。

:::note
一部のリンクが切れているようです。

公式ドキュメント等を参考にしてください:
- [Creating a personal access token (classic)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic) 
- [Creating secrets for a repository](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository)

:::

ここまでの操作を全て行うと、恐らく `oj-verify` が正しく動くはずです。[^0.0.3.1.]
適当に empty commit を打ったりして動作確認をしてください。
[^0.0.3.1.]: 動かなかったら教えてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1089746/83207797-1e3c-08fa-6210-b406b2e6b5a9.png)


:::note warn
自身のライブラリやテストが AC Libray に依存している人は、`verify` [job](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobs) 自体は失敗するはずです。これは [ステップ **0.1.**](#01-テスト時に-ac-library-を自動で-clone-してもらう) で修正します。
:::
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1089746/7b4e6e5f-8385-274c-3690-b0422bc12e19.png)


#### 0.0.4. GitHub Pages のデプロイ先を設定する
問題がなければ `gh-pages` というブランチが新たに生成されているはずです。  
これを GitHub Pages のデプロイ元に設定します。  

Settings > Pages > Build and deployment > Branch から、`gh-pages` のルートを選択して保存してください。[^0.0.4.]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1089746/c62b0c40-0a39-2e40-f3bb-fd2ac868d47c.png)


[^0.0.4.]: 元々そうなっている人はそのままでいいです。


`pages build and deployment` という workflow が走れば成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1089746/b9b6fd7a-837b-9d91-7bdc-cc5afd54e670.png)


正しく実行が終了すると、自動生成されたドキュメントへリンクがこの workflow の詳細の `deploy` job の欄に表示されるはずです。アクセスしてみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1089746/4aba6ec1-e0ae-a5a5-007f-d811e48db437.png)


ここまでで `oj-verify` を GitHub Actions 上で利用する準備は整いました。  

`AC Library` に依存していない かつ 速度面で困っていない 方はここでおしまいです。
お疲れ様でした。

<br/>

[**ここまでのサンプル**](https://github.com/uni-kakurenbo/parallel-verify-sample/tree/79937905d4b3d70bd2fb51581ec1eac9bb18ca70)

---

### 0.1. テスト時に `AC Library` を自動で clone してもらう
色々な人のライブラリを見ていると、ルートに `atcoder/` を置いているものをしばしば見かけます。  
ライセンス上は全く問題ないとはいえ追従もしづらいので、できればしたくないですよね。

ということで workflows 実行時に、勝手に clone してもらうようにしてみましょう。

#### 0.1.0. 競合を解消する

checkout が競合するので、それを解消します。

##### 0.1.0.0. checkout するディレクトリを変更する

自分のリポジトリが `${{ github.workspace }}/main/`[^github-context] 以下に clone されるようになります。
[^github-context]: [`${{ github.workspace }}`](https://docs.github.com/en/actions/learn-github-actions/contexts#github-context)

```diff_yaml:verify.yml
     - uses: actions/checkout@v3
       with:
+        path: main
         fetch-depth: 0
```

##### 0.1.0.1. テストを走らせるディレクトリを追従する

ライブラリが `main/` 以下にあるので、当然 `oj-verify` もそこで実行します。

```diff_yaml:verify.yml
     - name: Run tests
+      working-directory: main
       env:
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         YUKICODER_TOKEN: ${{ secrets.YUKICODER_TOKEN }}
         GH_PAT: ${{ secrets.GH_PAT }}
       run: oj-verify all
```

#### 0.1.1. AC Library を Clone する

##### 0.1.1.0. clone step を追加する

`Run tests` step より前に、以下の step を追記します。
```verify.yml
    - name: Clone AC-Library
      uses: actions/checkout@v3
      with:
        repository: atcoder/ac-library
        path: ac-library
        sparse-checkout: atcoder
```

##### 0.1.1.1. コンパイラに AC Library の所在を明かす

`Run test` step へ渡す環境変数に `CPLUS_INCLUDE_PATH` を追加します。[^cplus-include-path]
[^cplus-include-path]: 他の言語でもこのように指定する方法があるのかはわからないです。

```diff_yaml:verify.yml
     - name: Run tests
       env:
+        CPLUS_INCLUDE_PATH: ${{ github.workspace }}/ac-library
         ITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         YUKICODER_TOKEN: ${{ secrets.YUKICODER_TOKEN }}
         GH_PAT: ${{ secrets.GH_PAT }}
       run: oj-verify all
```

これで、AC Library の自動 clone も完了です。お疲れ様でした。

<br/>

[**ここまでのサンプル**](https://github.com/uni-kakurenbo/parallel-verify-sample/tree/cd6ecde630a25834e8a1a2a4bdf6a26ed2793591)

---

## 1. 並列化

ここから先は分量が格段に増えるので、GitHub 経由で差分を示します。

### 1.0. job の並列化
こちらは [matrix strategy](https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs) というものを利用することで比較的簡単に実装できます。

とはいえ差分はそれなりに多いです。

**差分**：

https://github.com/uni-kakurenbo/parallel-verify-sample/commit/cd447517c713f3bb125f30cabf3459d4e98ebbaf


各テストファイルの実行 job は、ファイルの辞書順インデクスを並列数で割ったあまりによって割り当てています。[^parallel-job-assign]
[^parallel-job-assign]: もっといい方法があるかもしれません。教えてください。

並列数は `jobs.verify.strategy.matrix.id` から適当に調整してください。  
`20` は [Free プランにおける job の最大並列数](https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration#usage-limits) です。

#### 簡単な解説

`oj-verify` は `.verify-helper/timestamps.remote.json` に最終テスト時刻[^last-test-date]のタイムスタンプをキャッシュしています。

各 job で異なる箇所が変更されるため、正しくタイムスタンプを更新するためにはそれらを統合する必要があります。

[workflow artifacts](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts#about-workflow-artifacts) という便利な仕組みがあるので、ここではそれを用いることで各 job によるタイムスタンプの変更を保存しています。
統合は、`post-verify` job にて `jq` を用いて簡単に行っています。

[^last-test-date]: remote の場合は、正確には「依存するファイルの最終 commit 時刻」です

<br/>

[**ここまでのサンプル**](https://github.com/uni-kakurenbo/parallel-verify-sample/tree/cd447517c713f3bb125f30cabf3459d4e98ebbaf)

---

### 1.1 プロセスの並列化
せっかくなので各 job 内のテストも並列化します。
これは job の並列化より少々大変そうです。[^oj-thread-safety]
[^oj-thread-safety]: これは主に `oj-verify` が thread-safe でないためです。(それはそう。)

**差分**：

https://github.com/uni-kakurenbo/parallel-verify-sample/commit/5727375472d659c5e6c2cef87f87968edcc52efe

`run-test.sh` は新しく作成してください。

とりあえず並列数は `$(nproc)` にしています。[^nproc]

[^nproc]: `Linux` 仮想マシンでは `4` っぽいです。でも結局コア数は `2` なので大して変わらない気がします。

:::note warn
同じ問題に対するテストが job の並列数 (今回は `20`) より多いと失敗します。  
`oj-verify` が問題ごとにプログラムの実行ファイルをスレッド間で共有するためです。[^parallel-exe]

[^parallel-exe]: マルチスレッドでの実行は想定されていないので当然です。
:::

:::note

[`run-test.sh`](https://github.com/uni-kakurenbo/parallel-verify-sample/blob/5727375472d659c5e6c2cef87f87968edcc52efe/.github/workflows/run-test.sh) [L30](https://github.com/uni-kakurenbo/parallel-verify-sample/blob/5727375472d659c5e6c2cef87f87968edcc52efe/.github/workflows/run-test.sh#L30), [L40](https://github.com/uni-kakurenbo/parallel-verify-sample/blob/5727375472d659c5e6c2cef87f87968edcc52efe/.github/workflows/run-test.sh#L40) の `::notice::` は必要に応じて変更してください。
うるさい場合は削除してもいいと思います。

:::

#### 簡単な解説

`xargs` を使います。

いかがでしたか？

---

タイムスタンプの書き換えを `oj-verify` ではなく workflow 側で取り持っています。(スレッド間で競合するので。)

また、Library Checker の問題に対するテストでは、初回に限り [yosupo06/library-checker-problems](https://github.com/yosupo06/library-checker-problems) を clone / pull する[らしい](https://github.com/online-judge-tools/api-client/blob/8529981e570c231770ac2347270623d29c9b14f9/onlinejudge/service/library_checker.py#L49)ので、若干強引ですが https://judge.yosupo.jp/problem/aplusb に対して `oj d` を叩くことで Git の競合を防いでいます。[^yospo-update]
[^yospo-update]: もっといい方法があるかもしれません。

## 2. テストケースをキャッシュする

GitHub Actions 上で [cache](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows) を手軽に扱える [`cache` action](https://github.com/actions/cache) というものがあるらしいです。
せっかくなので利用しましょう。

**差分**：

https://github.com/uni-kakurenbo/parallel-verify-sample/commit/321515cedd66ab17f6403ef1d0435a77860cdb6a

ほぼ `uses: actions/cache@v3` を書くだけですね。  

:::note
ここでは一旦、`verify/**/*.test.cpp` にマッチするファイルたち と 各 job に割り当たっているテストたち とによるハッシュをキャッシュの key に指定しています。
ここは適宜調整してください。[^cache-key]

[^cache-key]: 何もなければこのままで大丈夫だと思います。

```verify.yml
    - name: Cache judge data
      if: ${{ !cancelled() && github.ref_name == github.event.repository.default_branch }}
      id: cache-judge
      uses: actions/cache@v3
      with:
        path: |
          ~/.cache/online-judge-tools/library-checker-problems/
          main/.verify-helper/cache/
        key: verify-${{ hashFiles('main/verify/**/*.test.cpp', 'main/.verify-helper/targets.txt') }}

```

:::


<br/>

[**ここまでのサンプル**](https://github.com/uni-kakurenbo/parallel-verify-sample/tree/321515cedd66ab17f6403ef1d0435a77860cdb6a)

---


## 3. もう少しだけ使いやすく
[`on`](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on) に [`workflow_dispatch`](https://docs.github.com/ja/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch) を付与[^workflow-dispatch]したり、[`concurrency`](https://docs.github.com/en/actions/using-jobs/using-concurrency) を設定したりすると使いやすい気がします。  
[^workflow-dispatch]: [こういう使い方](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1089746/c0dd25e3-2201-8206-e0d7-cd072c73fb45.png)もできるようになります。いちいち empty commit を打ったりしなくていいので楽ですね。


workflow 自体も大きくなってしまったので、ファイルを分割しました。

各 job に割り当てるテスト数を `$(nproc)` に制限したりもしています。(その代わりに job 数を増やすようにしています。)
最大並列数 (`20`) を超えると待機する job が発生しますが、筆者の環境ではこちらの方が合計の実行時間は若干短かったです。

その他の細かい修正点は差分をご覧ください。というかお好みでどうぞ。

**差分**：

https://github.com/uni-kakurenbo/parallel-verify-sample/commit/dd01971a8c40da717e7633b281a611f3c8ac30cb

:::note
リポジトリ環境変数に `RUNNER_IMAGE` を設定してください。
値は `ubuntu-latest` か `ubuntu-22.04` で動くと思います。[^ubuntu-22.04]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1089746/52adc1cb-d760-54f8-f669-75746a5fbc44.png)


[^ubuntu-22.04]: 後者がよさそう？
:::


これで終わりです。お疲れ様でした。

<br/>

[**ここまでのサンプル**](https://github.com/uni-kakurenbo/parallel-verify-sample/tree/dd01971a8c40da717e7633b281a611f3c8ac30cb)

---

## 4. 改善案
沢山ある気がします。
- テスト済み問題は割り当て時に弾く
- 実行時間をキャシュして、いい感じに分配する
- `#define PROBLEM` を解析してキャッシュの容量を最小化する
- etc.

気が向いたら個人的に修正をしたりする可能性もあります。[^improvement]
[^improvement]: この記事に加筆する世界線も無くはないです。

筆者のライブラリを置いておきます。
ここで何かやっているかもしれません：

https://github.com/uni-kakurenbo/library-for-competitive-programming

# おわりに
ここまでくるとゼロから実装した方が速いかもという気もしてきます。[^scratch]
[^scratch]: 速くないかもしれません。

GitHub Actions のいいお勉強にはなりました。
ついでに Lint とか走らせてもいいかもです。

皆様におかれましては、快適なライブラリ盆栽ライフをお過ごしいただければと存じます。

## 参考文献
https://docs.github.com/en/actions

https://github.com/yosupo06/library-checker-problems/tree/f54fda245f479461a0ce35dea02d31095053b818

https://github.com/online-judge-tools/verification-helper/tree/master

https://github.com/online-judge-tools/api-client

https://tech.gunosy.io/entry/actions_parallel

https://swfz.hatenablog.com/entry/2021/06/29/195359

https://zenn.dev/shunsuke_suzuki/articles/pinact-pin-github-actions-version