Tests de performance
Le banc d'essai écrit des JSON stables et un rapport lisible. Il se trouve
dans bench/ du dépôt storh et ne fait plus partie du paquet Composer depuis 0.5.2. Les commandes composer bench* ci-dessous s'exécutent donc depuis un clone, après composer install :
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
Résultats de référence
Médianes de trois exécutions complètes du banc d'essai, avec 50,000 enregistrements par moteur, sur un Apple M1 Pro (16 GB, macOS 26.5, APFS) avec PHP 8.5 CLI, opcache et JIT désactivés :
composer bench:repeat -- --dataset=50000 --engine=all --repeat=3 --memory-limit=512M
Chaque écriture est vidée et synchronisée avec fsync avant que storh ne la signale comme stockée : les débits d'écriture dépendent donc du système de fichiers. Les lectures s'effectuent dans le cache de pages du système d'exploitation. Les débits sont calculés à partir des médianes ; exécutez composer bench depuis un clone du dépôt sur votre propre matériel.
| DocStore, 50k enregistrements | médiane |
|---|---|
put(), un fichier durable par enregistrement |
3.3k enregistrements/s |
putStream() ingestion en lot |
3.7k enregistrements/s |
importJsonl() |
3.6k enregistrements/s |
get() lecture ponctuelle, validée par STAT |
5.6 µs |
| réouverture d'un magasin existant | 0.26 ms |
requête d'égalité indexée, limit(100) |
1.1 ms |
count() indexée dans toute la collection |
41 µs |
| création d'index, 2 champs d'égalité + 1 de plage | 52k enregistrements/s |
stream() complet avec revalidation STAT |
103k enregistrements/s |
exportJsonl() |
104k enregistrements/s |
| SegmentedLog, 50k enregistrements, segments de 16 KB | médiane |
|---|---|
put(), fsync à chaque ajout |
16k ajouts/s |
appendStream() ingestion en lot |
56k enregistrements/s |
| lecture par curseur, 100 enregistrements depuis le milieu | 2.1 ms |
| lecture par plage temporelle | 1.1 ms |
count() d'égalité |
11 µs |
compact() tous les segments scellés |
69k enregistrements/s |
| réouverture avec récupération de fin tronquée | 218k enregistrements/s |
Le banc d'essai scelle un segment tous les 16 KB afin de provoquer des rotations ; la taille de segment par défaut est de 1 MiB.
| Queue, 50k tâches | médiane |
|---|---|
enqueue(), fsync à chaque événement |
22k tâches/s |
claim() |
23k tâches/s |
complete() |
23k tâches/s |
enqueueMany() |
209k tâches/s |
claimMany() |
467k tâches/s |
completeMany() |
512k tâches/s |
| Miroir SQL, SQLite, 50k enregistrements | médiane |
|---|---|
push() initiale |
61k lignes/s |
push() sans changement |
86k enregistrements/s |
flush(), 100 identifiants |
7.1 ms |
SQL indexée COUNT sur le miroir |
6.9 ms |
rebuild() |
65k lignes/s |
restauration pull(), un fichier durable par enregistrement |
3.2k enregistrements/s |
| Micro | médiane |
|---|---|
get() en cache, à froid puis à chaud (MemoryCache, STAT) |
83 µs / 6.3 µs |
| génération UUIDv7 | 1.3 µs |
| validation UUIDv7 | 0.30 µs |
| filtrage de prédicats en mémoire | 4.4M lignes/s |
Cibles couvertes :
- DocStore put, put en continu, export/import JSONL, get, delete, stream et création d'index
- requêtes DocStore avec index d'égalité, index composé, index de plage, plages ordonnées croissantes et décroissantes, décompte indexé, recherche par identifiant et sans index
- ajout à SegmentedLog, ajout en continu, lectures par curseur, lectures par plage temporelle, lectures de plages temporelles compactées, lectures par curseur et recherche d'identifiant avec QueryBuilder, décomptes, décomptes d'ajouts en lot, statistiques, compactage
- mise en file d'attente, ajout en lot, revendication, revendication en lot, finalisation, finalisation en lot, remise en file d'attente avec Queue
- filtrage par prédicats RecordQuery et QueryCondition sur des lignes en mémoire
- génération monotone UUIDv7, génération avec horodatages répartis, validation et extraction d'horodatage
- récupération après une ligne finale incomplète
- lectures du cache à froid et à chaud avec validation par hachage, statut ou confiance
Chemin de sortie par défaut :
build/bench-current.json
Les exécutions par plage écrivent un fichier JSON par jeu de données et moteur sous
build/bench-ranges/.
bench:gate compare deux fichiers JSON de banc d'essai et renvoie un code de sortie non nul si une métrique suivie est plus lente que le seuil configuré. Si aucune valeur --metric n'est fournie, toutes les métriques présentes dans le fichier de référence sont vérifiées.
bench:ci est la vérification rapide des régressions de GitHub Actions. Elle exécute un banc d'essai de 1k sur tous les moteurs, compare les métriques sélectionnées d'écriture, de lecture, de cache et de UUID avec la référence bench/baselines/ci-1k-all.json suivie, et applique un seuil par défaut large pour éviter le bruit normal du système d'exécution tout en détectant les baisses de performances importantes.
bench:jit exécute le même banc d'essai avec CLI opcache et le JIT de traçage activés, et désactive Xdebug pour le processus du banc d'essai afin que PHP puisse réellement activer le JIT. Le JSON généré inclut des options d'exécution qui indiquent si opcache et le JIT étaient actifs lors de cette exécution.