
# ベンチマーク

ベンチマークハーネスは安定したJSONと、人が読めるレポートを書き出します。`bench/`にあり、[storhリポジトリ](https://github.com/inline0/storh)に含まれています。0.5.2以降、Composerパッケージには含まれていないため、以下の`composer bench*`コマンドはクローン上で`composer install`の後にのみ実行できます。

```bash
composer bench
composer bench -- --dataset=100000 --engine=doc
composer bench -- --dataset=100000 --engine=log
composer bench -- --dataset=100000 --engine=queue
composer bench -- --dataset=100000 --engine=cache --cache-validation=trust
composer bench -- --dataset=100000 --engine=filter
composer bench -- --dataset=1000000 --engine=uuid
composer bench:jit -- --dataset=1000000 --engine=uuid
composer bench:repeat -- --dataset=100000 --engine=doc --repeat=5
composer bench:repeat -- --dataset=1000000 --engine=filter --repeat=5 --memory-limit=512M
composer bench:range -- --datasets=1000,10000,50000,100000
composer bench:range -- --datasets=100000 --engines=cache --memory-limit=512M
composer bench:compare build/bench-main.json build/bench-current.json
composer bench:gate -- build/bench-main.json build/bench-current.json --threshold=10 --metric=doc.put --metric=log.stream
composer bench:ci
```

## 参考結果

Apple M1 Pro（16 GB、macOS 26.5、APFS）でPHP 8.5 CLI、opcacheとJITを無効にし、各エンジンで50,000レコードを処理したハーネスの全3回実行における中央値：

```bash
composer bench:repeat -- --dataset=50000 --engine=all --repeat=3 --memory-limit=512M
```

すべての書き込みはstorhが保存済みと報告する前にフラッシュおよびfsyncされるため、書き込み速度はファイルシステムに制約されます。読み取りはOSのページキャッシュに対して行われます。速度は中央値から算出しています。ご自身のハードウェアで実行するには、リポジトリのクローンから`composer bench`を実行してください。

| DocStore、50kレコード                         | 中央値          |
| ---------------------------------------------- | --------------- |
| `put()`、レコードごとに永続化ファイル1つ       | 3.3k records/s  |
| `putStream()`一括取り込み                            | 3.7k records/s  |
| `importJsonl()`                                        | 3.6k records/s  |
| `get()`ポイント読み取り、STAT検証済み          | 5.6 µs          |
| 既存ストアの再オープン                        | 0.26 ms         |
| インデックス付き等価検索、`limit(100)`              | 1.1 ms          |
| コレクション全体のインデックス付き`count()`      | 41 µs           |
| インデックス構築、等価フィールド2つ＋範囲フィールド1つ | 52k records/s   |
| STATで再検証する完全な`stream()`                  | 103k records/s  |
| `exportJsonl()`                                        | 104k records/s  |

| SegmentedLog、50kレコード、16 KBセグメント    | 中央値          |
| ---------------------------------------------- | --------------- |
| `put()`、追記ごとにfsync                       | 16k appends/s   |
| `appendStream()`一括取り込み                            | 56k records/s   |
| カーソル読み取り、中央から100レコード         | 2.1 ms          |
| 時間範囲読み取り                              | 1.1 ms          |
| 等価`count()`                                    | 11 µs           |
| 封印済み全セグメントの`compact()`                  | 69k records/s   |
| 末尾不完全データの復旧を伴う再オープン        | 218k records/s  |

ベンチマークではセグメントの切り替えに負荷をかけるため、16 KBごとにセグメントを封印します。デフォルトのセグメントサイズは1 MiBです。

| キュー、50kジョブ                             | 中央値          |
| ---------------------------------------------- | --------------- |
| `enqueue()`、イベントごとにfsync                   | 22k jobs/s      |
| `claim()`                                        | 23k jobs/s      |
| `complete()`                                        | 23k jobs/s      |
| `enqueueMany()`                                        | 209k jobs/s     |
| `claimMany()`                                        | 467k jobs/s     |
| `completeMany()`                                        | 512k jobs/s     |

| SQLミラー、SQLite、50kレコード             | 中央値          |
| ---------------------------------------------- | --------------- |
| 初回`push()`                                    | 61k rows/s      |
| 変更なしで`push()`                              | 86k records/s   |
| `flush()`、100 id                                 | 7.1 ms          |
| ミラー上のインデックス付きSQL`COUNT`        | 6.9 ms          |
| `rebuild()`                                        | 65k rows/s      |
| `pull()`復元、レコードごとに永続化ファイル1つ   | 3.2k records/s  |

| マイクロベンチマーク                           | 中央値          |
| ---------------------------------------------- | --------------- |
| キャッシュ済み`get()`、コールドからウォームへ（MemoryCache、STAT） | 83 µs / 6.3 µs |
| UUIDv7の生成                                   | 1.3 µs          |
| UUIDv7の検証                                   | 0.30 µs         |
| メモリ内の述語フィルタリング                   | 4.4M rows/s     |

対象範囲：

- DocStoreのput、ストリーミングput、JSONLエクスポート／インポート、get、delete、stream、インデックス構築
- 等価インデックス、複合インデックス、範囲インデックス、昇順・降順の範囲ソート、インデックス付き件数取得、ID検索、インデックスなしのDocStoreクエリ
- SegmentedLogのappend、ストリーミングappend、カーソル読み取り、時間範囲読み取り、圧縮済み時間範囲読み取り、QueryBuilderのカーソルとID検索、件数取得、一括appendの件数取得、統計、圧縮
- Queueのenqueue、一括enqueue、claim、一括claim、complete、一括complete、requeue
- メモリ内の行に対するRecordQueryとQueryConditionの述語フィルタリング
- UUIDv7の単調生成、タイムスタンプ分散生成、検証、タイムスタンプ抽出
- 末尾の不完全な行の復旧
- ハッシュ、stat、信頼済み検証を使ったコールド／ウォームキャッシュの読み取り

デフォルトの出力パス：

```text
build/bench-current.json
```

範囲実行では、`build/bench-ranges/`の下にデータセットとエンジンごとにJSONファイルを1つ書き込みます。

`bench:gate`は2つのベンチマークJSONファイルを比較し、追跡対象の指標が設定されたしきい値より遅い場合はゼロ以外の終了コードを返します。`--metric`の値が指定されていない場合、ベースラインファイルにあるすべての指標を判定します。

`bench:ci`はGitHub Actionsの簡易回帰ゲートです。全エンジンで1kベンチマークを実行し、選択した書き込み／読み取り／キャッシュ／UUID指標を追跡対象の`bench/baselines/ci-1k-all.json`ベースラインと比較します。ランナーで通常起きるばらつきを許容しつつ、大幅な性能低下は検出できるよう、デフォルトのしきい値は広めに設定されています。

`bench:jit`は同じハーネスをCLI opcacheとトレーシングJITを有効にして実行し、ベンチマークプロセスではXdebugを無効にしてPHPが実際にJITを有効化できるようにします。生成されるJSONには、その実行でopcacheとJITが有効だったかを示すランタイムフラグが含まれます。
