Pular para o conteúdo

Benchmarks

O harness de benchmark grava JSON estáveis e um relatório legível. Ele fica em bench/ no repositório 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:

Shell
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:

Shell
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.