Testy wydajnościowe
Zestaw testów wydajności zapisuje stabilne JSON oraz czytelny dla człowieka raport. Znajduje się
w bench/ w repozytorium storh i nie jest
częścią pakietu Composer od wersji 0.5.2, dlatego poniższe
polecenia composer bench* można uruchomić tylko z klonu repozytorium, po 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
Wyniki referencyjne
Mediany z trzech pełnych przebiegów zestawu testów, po 50,000 rekordów na silnik, na Apple M1 Pro (16 GB, macOS 26.5, APFS) z PHP 8.5 CLI, opcache i JIT wyłączonymi:
composer bench:repeat -- --dataset=50000 --engine=all --repeat=3 --memory-limit=512M
Każdy zapis jest opróżniany z bufora i synchronizowany przez fsync, zanim storh zgłosi, że został zapisany, więc szybkość zapisu zależy od systemu plików. Odczyty korzystają z pamięci podręcznej stron systemu operacyjnego. Szybkości obliczono na podstawie median; uruchom composer bench z klonu repozytorium, aby sprawdzić działanie na własnym sprzęcie.
| DocStore, 50 tys. rekordów | mediana |
|---|---|
put(), jeden trwały plik na rekord |
3.3 tys. rekordów/s |
masowe pobieranie putStream() |
3.7 tys. rekordów/s |
importJsonl() |
3.6 tys. rekordów/s |
odczyt punktowy get(), z weryfikacją STAT |
5.6 µs |
| ponowne otwarcie istniejącego magazynu | 0.26 ms |
indeksowane zapytanie równościowe, limit(100) |
1.1 ms |
indeksowane count() w całej kolekcji |
41 µs |
| budowanie indeksu, 2 pola równościowe + 1 zakresu | 52 tys. rekordów/s |
pełne stream() z ponowną weryfikacją STAT |
103 tys. rekordów/s |
exportJsonl() |
104 tys. rekordów/s |
| SegmentedLog, 50 tys. rekordów, segmenty 16 KB | mediana |
|---|---|
put(), fsync przy każdym dopisaniu |
16 tys. dopisań/s |
masowe pobieranie appendStream() |
56 tys. rekordów/s |
| odczyt kursorem, 100 rekordów od środka | 2.1 ms |
| odczyt z zakresu czasu | 1.1 ms |
indeks równościowy count() |
11 µs |
compact() wszystkich zamkniętych segmentów |
69 tys. rekordów/s |
| ponowne otwarcie z odzyskiwaniem niekompletnego końca | 218 tys. rekordów/s |
Test wydajności zamyka segment co 16 KB, aby obciążyć rotację segmentów; domyślny rozmiar segmentu to 1 MiB.
| Queue, 50 tys. zadań | mediana |
|---|---|
enqueue(), fsync przy każdym zdarzeniu |
22 tys. zadań/s |
claim() |
23 tys. zadań/s |
complete() |
23 tys. zadań/s |
enqueueMany() |
209 tys. zadań/s |
claimMany() |
467 tys. zadań/s |
completeMany() |
512 tys. zadań/s |
| SQL Mirror, SQLite, 50 tys. rekordów | mediana |
|---|---|
początkowe push() |
61 tys. wierszy/s |
push() bez żadnych zmian |
86 tys. rekordów/s |
flush(), identyfikatory 100 |
7.1 ms |
indeksowany SQL COUNT w całym mirrorze |
6.9 ms |
rebuild() |
65 tys. wierszy/s |
przywracanie pull(), jeden trwały plik na rekord |
3.2 tys. rekordów/s |
| Micro | mediana |
|---|---|
buforowane get(), zimne, a następnie ciepłe (MemoryCache, STAT) |
83 µs / 6.3 µs |
| generowanie UUIDv7 | 1.3 µs |
| weryfikacja UUIDv7 | 0.30 µs |
| filtrowanie predykatów w pamięci | 4.4 mln wierszy/s |
Objęte testami cele:
- DocStore put, put strumieniowe, JSONL eksport/import, get, delete, stream i budowanie indeksu
- zapytania DocStore z indeksami równościowymi, złożonymi i zakresowymi, uporządkowanymi zakresami rosnącymi i malejącymi, zliczaniem indeksowanym, wyszukiwaniem po ID oraz zapytania bez indeksu
- SegmentedLog append, append strumieniowe, odczyty kursorem, odczyty zakresu czasu, odczyty skompaktowanego zakresu czasu, wyszukiwanie kursorem i po ID przez QueryBuilder, zliczanie, zliczanie masowych zapisów, statystyki, kompaktowanie
- Queue enqueue, masowe enqueue, claim, masowe claim, complete, masowe complete, requeue
- filtrowanie predykatami RecordQuery i QueryCondition wierszy przechowywanych w pamięci
- UUIDv7 generowanie monotoniczne, generowanie z rozłożeniem znaczników czasu, walidacja i wyodrębnianie znaczników czasu
- odzyskiwanie po urwanej końcowej linii
- odczyty z zimnej i ciepłej pamięci podręcznej z walidacją przez hash, stat lub zaufaną walidację
Domyślna ścieżka wyjściowa:
build/bench-current.json
Uruchomienia zakresowe zapisują jeden plik JSON dla każdego zbioru danych i silnika w
build/bench-ranges/.
bench:gate porównuje dwa pliki JSON z benchmarkami i kończy działanie kodem różnym od zera, gdy dowolna śledzona metryka jest wolniejsza niż skonfigurowany próg. Jeśli nie przekazano wartości --metric, sprawdza każdą metrykę z pliku bazowego.
bench:ci to bramka regresji dymnej GitHub Actions. Uruchamia benchmark 1k dla wszystkich silników, porównuje wybrane metryki zapisu/odczytu/pamięci podręcznej/UUID z śledzonym punktem odniesienia bench/baselines/ci-1k-all.json i stosuje szeroki domyślny próg, aby uniknąć typowych wahań środowiska uruchomieniowego, a zarazem wykrywać duże regresje wydajności.
bench:jit uruchamia ten sam zestaw testowy z włączonym CLI opcache i JIT śledzącym, a dla procesu benchmarku wyłącza Xdebug, aby PHP mógł rzeczywiście włączyć JIT.
Wygenerowany JSON zawiera flagi środowiska uruchomieniowego pokazujące, czy opcache i JIT były aktywne podczas tego uruchomienia.