
# Benchmarks

O harness de benchmark grava JSON estáveis e um relatório legível. Ele fica
em `bench/` no repositório [storh](https://github.com/inline0/storh) e não faz parte do pacote Composer desde 0.5.2; por isso, os comandos `composer bench*` abaixo só podem ser executados a partir de um clone, após `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
```

## Resultados de referência

Medianas de três execuções completas do harness, com 50,000 registros por mecanismo, em um
Apple M1 Pro (16 GB, macOS 26.5, APFS) com PHP 8.5 CLI, opcache e JIT desativados:

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

Cada gravação é descarregada e sincronizada com fsync antes de storh informar que foi armazenada, portanto as taxas de gravação são limitadas pelo sistema de arquivos. As leituras usam o cache de páginas do sistema operacional. As taxas são
calculadas a partir das medianas; execute `composer bench` a partir de um clone do repositório
no seu próprio hardware.

| DocStore, 50 mil registros                   | mediana         |
| ---------------------------------------------- | --------------- |
| `put()`, um arquivo durável por registro        | 3.3k registros/s |
| ingestão em lote `putStream()`                       | 3.7k registros/s |
| `importJsonl()`                                        | 3.6k registros/s |
| leitura pontual `get()`, validada por STAT       | 5.6 µs          |
| reabertura de um armazenamento existente       | 0.26 ms         |
| consulta por igualdade indexada, `limit(100)`        | 1.1 ms          |
| `count()` indexada na coleção                    | 41 µs           |
| criação de índice, 2 campos de igualdade + 1 de intervalo | 52k registros/s |
| `stream()` completo com revalidação STAT          | 103k registros/s|
| `exportJsonl()`                                        | 104k registros/s|

| SegmentedLog, 50 mil registros, segmentos de 16 KB | mediana          |
| ---------------------------------------------- | --------------- |
| `put()`, fsync por acréscimo                     | 16k acréscimos/s |
| ingestão em lote `appendStream()`                        | 56k registros/s |
| leitura com cursor, 100 registros do meio    | 2.1 ms          |
| leitura por intervalo de tempo                 | 1.1 ms          |
| `count()` por igualdade                           | 11 µs           |
| `compact()` de todos os segmentos selados            | 69k registros/s |
| reabertura com recuperação de final incompleto  | 218k registros/s|

O benchmark sela um segmento a cada 16 KB para testar a rotação de segmentos; o
tamanho padrão do segmento é 1 MiB.

| Fila, 50 mil tarefas                          | mediana         |
| ---------------------------------------------- | --------------- |
| `enqueue()`, fsync por evento                       | 22k tarefas/s   |
| `claim()`                                        | 23k tarefas/s   |
| `complete()`                                        | 23k tarefas/s   |
| `enqueueMany()`                                        | 209k tarefas/s  |
| `claimMany()`                                        | 467k tarefas/s  |
| `completeMany()`                                        | 512k tarefas/s  |

| SQL Mirror, SQLite, 50 mil registros         | mediana          |
| ---------------------------------------------- | --------------- |
| `push()` inicial                                | 61k linhas/s    |
| `push()` sem alterações                         | 86k registros/s|
| `flush()`, IDs 100                             | 7.1 ms          |
| SQL `COUNT` indexada na mirror               | 6.9 ms          |
| `rebuild()`                                        | 65k linhas/s    |
| restauração `pull()`, um arquivo durável por registro | 3.2k registros/s|

| Micro                                          | mediana         |
| ---------------------------------------------- | --------------- |
| `get()` em cache, frio e depois aquecido (MemoryCache, STAT) | 83 µs / 6.3 µs |
| geração UUIDv7                                | 1.3 µs          |
| validação UUIDv7                               | 0.30 µs         |
| filtragem de predicados em memória             | 4.4M linhas/s   |

Alvos cobertos:

- DocStore put, streaming put, exportação/importação JSONL, get, delete, stream e criação de índice
- consultas do DocStore por igualdade, índice composto, intervalo, intervalo ordenado crescente e decrescente, contagem indexada, busca por ID e sem índice
- acréscimo no SegmentedLog, acréscimo em fluxo, leituras com cursor, leituras por intervalo de tempo, leituras compactadas por intervalo de tempo, buscas com cursor e por ID no QueryBuilder, contagens, contagens de acréscimos em lote, estatísticas e compactação
- enfileirar, enfileirar em lote, reivindicar, reivindicar em lote, concluir, concluir em lote e recolocar na fila
- filtragem de predicados de RecordQuery e QueryCondition em linhas na memória
- geração monotônica UUIDv7, geração com distribuição de timestamps, validação e extração de timestamp
- recuperação de linha final incompleta
- leituras de cache frio e aquecido com validação por hash, stat ou confiável

Caminho de saída padrão:

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

As execuções de intervalo gravam um arquivo JSON por conjunto de dados e mecanismo em
`build/bench-ranges/`.

`bench:gate` compara dois arquivos de JSON de benchmark e termina com código diferente de zero quando qualquer
métrica monitorada é mais lenta que o limite configurado. Se nenhum valor `--metric` for passado,
o controle se aplica a todas as métricas presentes no arquivo de referência.

`bench:ci` é o controle rápido de regressão GitHub Actions. Executa um benchmark de 1k com todos os mecanismos,
compara métricas selecionadas de gravação/leitura/cache/UUID com a referência `bench/baselines/ci-1k-all.json` monitorada e usa um limite padrão amplo para
ignorar oscilações normais do executor, mas ainda detectar regressões grandes de desempenho.

`bench:jit` executa o mesmo harness com opcache CLI e JIT de rastreamento ativados,
e desativa Xdebug para o processo de benchmark, para que PHP possa realmente ativar o JIT.
O JSON gerado inclui flags de execução que mostram se opcache e JIT
estavam ativos nessa execução.
