Skip to content

fix(ci): make nightly Valgrind job load the extension; guard bench tests under memcheck - #204

Merged
orieg merged 1 commit into
mainfrom
claude/php-judy-issue-202-11b62c
Aug 26, 2026
Merged

fix(ci): make nightly Valgrind job load the extension; guard bench tests under memcheck#204
orieg merged 1 commit into
mainfrom
claude/php-judy-issue-202-11b62c

Conversation

@orieg

@orieg orieg commented Aug 26, 2026

Copy link
Copy Markdown
Owner

Summary

The nightly Valgrind job (introduced in #201) has failed on every run since it was added — but not because of a memory leak. It invoked run-tests.php directly, which never loads the freshly built modules/judy.so, so 227/232 tests silently SKIPped on extension_loaded('judy') and the only tests that executed were the three bench-harness tests, which genuinely misbehave under memcheck.

Changes

  • nightly.yml: run the suite via make test TESTS="-j$(nproc) -m -q --show-diff -d pcre.jit=0 tests/" so the built extension is loaded (make test passes -d extension_dir=modules -d extension=judy.so; the generated Makefile appends TESTS to the run-tests invocation). pcre.jit=0 is required because PCRE2's sljit-generated code trips uninitialised-value reports inside the php binary itself (no judy.so frame), flagging every preg_*-using test as LEAK. The auto-filed issue's repro command is corrected to match.
  • tests: the three bench-harness tests (bench_compare_drift_001, bench_memory_limit_001, bench_memory_measurement_001) spawn child PHP processes, and run-tests' memcheck mode uses --trace-children=yes: children's RSS is inflated by an order of magnitude (breaking the heap-vs-RSS assertions) and child memory_limit fatals leave allocations unfreed by design, which memcheck attributes to the test as a leak. They now skip when USE_ZEND_ALLOC=0 is exported — the standard run-tests marker for a Valgrind run.

Verification

  • Linux container (PHP 8.4, valgrind), exact fixed command: 229/232 PASS under memcheck, 0 failed, 0 leaked, 3 skipped (the guarded bench tests), exit 0.
  • Without pcre.jit=0, the only reports are uninitialised-value warnings in PCRE2 JIT code with no judy.so frames; with it, all 8 preg_*-using tests are clean.
  • Normal (non-Valgrind) suite: 232/232 PASS, 0 skipped — the new guards do not fire outside memcheck runs.

Fixes #202

…sts under memcheck

The nightly Valgrind job (new in #201) invoked run-tests.php directly,
which never loads the freshly built modules/judy.so — 227/232 tests
silently SKIPped on extension_loaded('judy') and the job has failed on
every run since it was introduced. Switch to make test, which passes
-d extension_dir=modules -d extension=judy.so; the -m/-q/--show-diff
flags ride in TESTS, which the generated Makefile appends to the
run-tests.php invocation.

Two classes of false positives surface once the suite actually runs
under memcheck (--trace-children=yes, USE_ZEND_ALLOC=0):

- The three bench-harness tests spawn child PHP processes. Children are
  traced too: memcheck inflates their RSS by an order of magnitude
  (breaking bench_memory_measurement_001's heap-vs-RSS assertions) and
  child memory_limit fatals leave allocations unfreed by design, which
  memcheck attributes to the test as a leak (bench_memory_limit_001).
  All three now skip when USE_ZEND_ALLOC=0 is exported, the standard
  run-tests marker for a Valgrind run.

- PCRE2's sljit-generated code trips uninitialised-value reports inside
  the php binary itself (no judy.so frame), flagging every preg_*-using
  test as LEAK. -d pcre.jit=0 in the Valgrind run silences it.

Verified in a Linux container (PHP 8.4, valgrind): the fixed command
runs 229/232 under memcheck with 0 failures and 0 leaks (3 skips are
the guarded bench tests); the normal suite still passes 232/232 with
0 skips.

Fixes #202
@github-actions

Copy link
Copy Markdown

Test Results

PHP Platform Arch TS Tests Pass Fail Skip Duration
8.1 Linux x64 - 232 232 0 0 9.1s
8.2 Linux x64 - 232 232 0 0 8.4s
8.3 Linux x64 - 232 232 0 0 10.2s
8.4 Linux x64 - 232 232 0 0 9.8s
8.5 Linux x64 - 232 232 0 0 8.9s
✅ 🧪 8.6 Linux x64 - 232 232 0 0 13.5s
8.1 Linux (Expanse) x64 - 232 232 0 0 9.0s
8.2 Linux (Expanse) x64 - 232 232 0 0 9.9s
8.3 Linux (Expanse) x64 - 232 232 0 0 7.1s
8.4 Linux (Expanse) x64 - 232 232 0 0 10.2s
8.5 Linux (Expanse) x64 - 232 232 0 0 9.6s
✅ 🧪 8.6 Linux (Expanse) x64 - 232 232 0 0 11.6s
8.1 Windows x64 nts 232 232 0 0 13.5s
8.2 Windows x64 nts 232 232 0 0 13.4s
8.3 Windows x64 nts 232 232 0 0 14.0s
8.4 Windows x64 nts 232 232 0 0 13.5s
8.5 Windows x64 nts 232 232 0 0 14.5s
✅ 🧪 8.6 Windows x64 nts 232 232 0 0 13.2s
8.1 Windows (Expanse) x64 nts 232 232 0 0 13.4s
8.2 Windows (Expanse) x64 nts 232 232 0 0 13.5s
8.3 Windows (Expanse) x64 nts 232 232 0 0 16.7s
8.4 Windows (Expanse) x64 nts 232 232 0 0 13.7s
8.5 Windows (Expanse) x64 nts 232 232 0 0 13.3s
✅ 🧪 8.6 Windows (Expanse) x64 nts 232 232 0 0 9.6s
Total 5568 5568 0 0

Benchmark Results

500,000 elements, 7 iterations (median), Judy 2.7.1

Run Status

Platform PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5 PHP 8.6
Linux
Windows

Memory (Linux, PHP 8.6)

Peak RSS of a child process building one structure, n = 500,000, minus an empty-process floor. Judy's memory advantage is type- and scale-dependent — see BENCHMARK.md for the curve across sizes on a dedicated host.

Type Judy PHP array Judy vs PHP array
Bitset < 528.0 KB 8.0 MB > 16x smaller
Int to Int 4.3 MB 8.2 MB 1.9x smaller
Int to Mixed 40.4 MB 38.0 MB 1.06x larger
Int to Packed 16.3 MB 38.1 MB 2.3x smaller
String to Int 10.1 MB 38.0 MB 3.8x smaller
String to Mixed 46.2 MB 68.1 MB 1.5x smaller
String to Int (Hash) 31.5 MB 38.1 MB 1.2x smaller
String to Mixed (Hash) 67.3 MB 68.1 MB about the same
String to Int (Adaptive) 31.5 MB 38.1 MB 1.2x smaller
String to Mixed (Adaptive) 67.4 MB 68.1 MB about the same

< marks a structure smaller than this instrument can resolve at n = 500,000: run-to-run movement in the empty-process floor puts the resolution at 528.0 KB. The bound is real, the exact figure is not available at this size — BENCHMARK.md measures the same types up to n = 8M, where they are well clear of the floor.

API Speedups (Linux, PHP 8.6)

Speedup = PHP time / Judy time. Bold = Judy faster.

Operation Judy PHP Speedup
putAll() 20.4 ms 29.2 ms 1.4x
fromArray() 20.5 ms 29.2 ms 1.4x
toArray() 26.4 ms 40.8 ms 1.5x
getAll() 1.1 ms 2.1 ms 1.9x
keys() 12.4 ms 27.4 ms 2.2x
values() 13.4 ms 27.3 ms 2.0x
sumValues() 8.1 ms 21.7 ms 2.7x
populationCount() 0.00 ms 24.8 ms O(1) vs O(n)
deleteRange() 39.5 ms 55.7 ms 1.4x
equals() 11.4 ms 48.4 ms 4.2x
union() Bitset 30.8 ms 4.3 ms 0.1x
intersect() Bitset 13.2 ms 5.4 ms 0.4x
diff() Bitset 13.4 ms 3.3 ms 0.2x
xor() Bitset 24.9 ms 11.0 ms 0.4x
mergeWith() Int→Int 51.0 ms 68.3 ms 1.3x
mergeWith() Str→Int 235.7 ms 251.6 ms 1.1x
forEach() Int→Int 28.8 ms 26.1 ms 0.9x
filter() Int→Int 43.7 ms 40.9 ms 0.9x
map() Int→Int 48.2 ms 48.8 ms ~1.0x
forEach() Str→Int 65.3 ms 61.0 ms 0.9x
filter() Str→Int 120.4 ms 116.1 ms ~1.0x
map() Str→Int 156.4 ms 158.6 ms ~1.0x

O(1) vs O(n) marks a row whose ratio is decided by the complexity classes rather than measured: populationCount() reads libJudy's cached population, the PHP arm counts every element. It scales with n and says nothing about this change. judy-bench.php declines the same comparison for size($lo, $hi) for the same reason.

Core Types (Linux, PHP 8.6)

Speedup = PHP time / Judy time. Bold = Judy faster.

Type Op Judy PHP array Speedup
Bitset Write 20.3 ms 8.9 ms 0.4x
Bitset Read 12.9 ms 5.3 ms 0.4x
Bitset Iter 20.4 ms 3.7 ms 0.2x
Bitset Free 0.01 ms 0.37 ms 47.7x
Int to Int Write 24.7 ms 8.0 ms 0.3x
Int to Int Read 12.0 ms 5.5 ms 0.5x
Int to Int Iter 22.7 ms 3.7 ms 0.2x
Int to Int Free 0.44 ms 0.37 ms 0.8x
Int to Mixed Write 36.3 ms 12.4 ms 0.3x
Int to Mixed Read 14.2 ms 6.7 ms 0.5x
Int to Mixed Iter 22.7 ms 4.0 ms 0.2x
Int to Mixed Free 16.6 ms 5.0 ms 0.3x
String to Int Write 92.9 ms 16.1 ms 0.2x
String to Int Read 40.1 ms 7.5 ms 0.2x
String to Int Iter 57.5 ms 4.9 ms 0.1x
String to Int Free 20.8 ms 1.0 ms 0.0x
String to Int (Adaptive) Write 167.2 ms 16.0 ms 0.1x
String to Int (Adaptive) Read 26.6 ms 7.5 ms 0.3x
String to Int (Adaptive) Iter 71.1 ms 4.9 ms 0.1x
String to Int (Adaptive) Free 46.5 ms 1.1 ms 0.0x
All types detail

Core Types (Linux, PHP 8.6) — All

Speedup = PHP time / Judy time. Bold = Judy faster.

Type Op Judy PHP array Speedup
Bitset Write 20.3 ms 8.9 ms 0.4x
Bitset Read 12.9 ms 5.3 ms 0.4x
Bitset Iter 20.4 ms 3.7 ms 0.2x
Bitset Free 0.01 ms 0.37 ms 47.7x
Int to Int Write 24.7 ms 8.0 ms 0.3x
Int to Int Read 12.0 ms 5.5 ms 0.5x
Int to Int Iter 22.7 ms 3.7 ms 0.2x
Int to Int Free 0.44 ms 0.37 ms 0.8x
Int to Mixed Write 36.3 ms 12.4 ms 0.3x
Int to Mixed Read 14.2 ms 6.7 ms 0.5x
Int to Mixed Iter 22.7 ms 4.0 ms 0.2x
Int to Mixed Free 16.6 ms 5.0 ms 0.3x
Int to Packed Write 48.2 ms 11.6 ms 0.2x
Int to Packed Read 29.6 ms 6.6 ms 0.2x
Int to Packed Iter 38.4 ms 4.0 ms 0.1x
Int to Packed Free 10.1 ms 5.4 ms 0.5x
String to Int Write 92.9 ms 16.1 ms 0.2x
String to Int Read 40.1 ms 7.5 ms 0.2x
String to Int Iter 57.5 ms 4.9 ms 0.1x
String to Int Free 20.8 ms 1.0 ms 0.0x
String to Mixed Write 117.9 ms 20.1 ms 0.2x
String to Mixed Read 43.9 ms 10.2 ms 0.2x
String to Mixed Iter 72.2 ms 6.4 ms 0.1x
String to Mixed Free 70.7 ms 7.7 ms 0.1x
String to Int (Hash) Write 165.4 ms 16.0 ms 0.1x
String to Int (Hash) Read 27.2 ms 7.7 ms 0.3x
String to Int (Hash) Iter 72.1 ms 4.8 ms 0.1x
String to Int (Hash) Free 47.0 ms 1.0 ms 0.0x
String to Mixed (Hash) Write 248.4 ms 20.0 ms 0.1x
String to Mixed (Hash) Read 31.0 ms 10.3 ms 0.3x
String to Mixed (Hash) Iter 99.1 ms 6.5 ms 0.1x
String to Mixed (Hash) Free 132.3 ms 7.8 ms 0.1x
String to Int (Adaptive) Write 167.2 ms 16.0 ms 0.1x
String to Int (Adaptive) Read 26.6 ms 7.5 ms 0.3x
String to Int (Adaptive) Iter 71.1 ms 4.9 ms 0.1x
String to Int (Adaptive) Free 46.5 ms 1.1 ms 0.0x
String to Mixed (Adaptive) Write 267.2 ms 20.3 ms 0.1x
String to Mixed (Adaptive) Read 30.2 ms 10.4 ms 0.3x
String to Mixed (Adaptive) Iter 111.7 ms 6.6 ms 0.1x
String to Mixed (Adaptive) Free 136.7 ms 7.7 ms 0.1x
Cross-version detail (Linux)

Core Types — Linux

Each cell: Judy ms (speedup vs PHP array).

Type Op PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5 PHP 8.6
Bitset Write 20.1 ms (0.4x) 14.4 ms (0.4x) 18.7 ms (0.4x) 17.4 ms (0.4x) 14.8 ms (0.5x) 20.3 ms (0.4x)
Bitset Read 12.1 ms (0.4x) 9.6 ms (0.3x) 11.4 ms (0.4x) 10.9 ms (0.4x) 9.7 ms (0.4x) 12.9 ms (0.4x)
Int to Int Write 26.7 ms (0.3x) 20.7 ms (0.3x) 25.0 ms (0.3x) 23.2 ms (0.3x) 20.9 ms (0.3x) 24.7 ms (0.3x)
Int to Int Read 12.0 ms (0.4x) 10.5 ms (0.3x) 11.2 ms (0.4x) 11.0 ms (0.4x) 10.4 ms (0.4x) 12.0 ms (0.5x)
Int to Mixed Write 36.0 ms (0.4x) 30.5 ms (0.3x) 34.9 ms (0.3x) 34.4 ms (0.3x) 31.5 ms (0.4x) 36.3 ms (0.3x)
Int to Mixed Read 14.1 ms (0.5x) 11.2 ms (0.4x) 14.1 ms (0.4x) 14.0 ms (0.4x) 12.8 ms (0.5x) 14.2 ms (0.5x)
Int to Packed Write 50.0 ms (0.3x) 40.5 ms (0.2x) 45.5 ms (0.2x) 47.7 ms (0.2x) 43.3 ms (0.3x) 48.2 ms (0.2x)
Int to Packed Read 29.2 ms (0.2x) 27.3 ms (0.2x) 28.3 ms (0.3x) 30.0 ms (0.2x) 29.9 ms (0.2x) 29.6 ms (0.2x)
String to Int Write 92.0 ms (0.2x) 88.1 ms (0.2x) 92.7 ms (0.2x) 90.3 ms (0.2x) 91.9 ms (0.2x) 92.9 ms (0.2x)
String to Int Read 40.3 ms (0.2x) 36.2 ms (0.2x) 40.7 ms (0.2x) 37.4 ms (0.2x) 39.2 ms (0.2x) 40.1 ms (0.2x)
String to Mixed Write 117.6 ms (0.2x) 115.3 ms (0.2x) 118.5 ms (0.2x) 114.0 ms (0.2x) 108.8 ms (0.2x) 117.9 ms (0.2x)
String to Mixed Read 43.6 ms (0.2x) 38.2 ms (0.2x) 43.8 ms (0.2x) 40.9 ms (0.2x) 39.8 ms (0.2x) 43.9 ms (0.2x)
String to Int (Hash) Write 172.1 ms (0.1x) 158.0 ms (0.1x) 164.4 ms (0.1x) 164.7 ms (0.1x) 158.3 ms (0.1x) 165.4 ms (0.1x)
String to Int (Hash) Read 27.8 ms (0.3x) 21.3 ms (0.3x) 27.5 ms (0.3x) 25.3 ms (0.3x) 23.0 ms (0.3x) 27.2 ms (0.3x)
String to Mixed (Hash) Write 247.8 ms (0.1x) 235.6 ms (0.1x) 244.5 ms (0.1x) 231.4 ms (0.1x) 230.2 ms (0.1x) 248.4 ms (0.1x)
String to Mixed (Hash) Read 30.6 ms (0.3x) 22.9 ms (0.3x) 31.6 ms (0.3x) 28.2 ms (0.4x) 24.6 ms (0.4x) 31.0 ms (0.3x)
String to Int (Adaptive) Write 166.1 ms (0.1x) 154.2 ms (0.1x) 159.6 ms (0.1x) 164.4 ms (0.1x) 157.8 ms (0.1x) 167.2 ms (0.1x)
String to Int (Adaptive) Read 26.6 ms (0.3x) 21.0 ms (0.3x) 26.8 ms (0.3x) 24.4 ms (0.3x) 22.4 ms (0.4x) 26.6 ms (0.3x)
String to Mixed (Adaptive) Write 263.5 ms (0.1x) 256.6 ms (0.1x) 256.0 ms (0.1x) 238.0 ms (0.1x) 247.7 ms (0.1x) 267.2 ms (0.1x)
String to Mixed (Adaptive) Read 29.7 ms (0.3x) 21.4 ms (0.3x) 30.1 ms (0.4x) 27.0 ms (0.4x) 23.4 ms (0.4x) 30.2 ms (0.3x)

API — Linux

Operation PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5 PHP 8.6
putAll() 19.9 ms (1.4x) 18.3 ms (1.4x) 19.7 ms (1.4x) 19.5 ms (1.4x) 17.5 ms (1.6x) 20.4 ms (1.4x)
fromArray() 19.9 ms (1.4x) 18.3 ms (1.4x) 21.0 ms (1.3x) 19.5 ms (1.4x) 17.5 ms (1.6x) 20.5 ms (1.4x)
toArray() 22.8 ms (1.5x) 22.2 ms (1.5x) 22.1 ms (1.6x) 27.0 ms (1.5x) 24.5 ms (1.5x) 26.4 ms (1.5x)
getAll() 1.1 ms (2.0x) 1.0 ms (1.8x) 1.1 ms (2.0x) 1.1 ms (1.9x) 1.3 ms (1.8x) 1.1 ms (1.9x)
keys() 12.4 ms (2.2x) 10.7 ms (2.0x) 11.1 ms (2.3x) 12.3 ms (2.1x) 10.1 ms (2.1x) 12.4 ms (2.2x)
values() 13.8 ms (2.0x) 11.2 ms (1.8x) 11.6 ms (2.2x) 13.6 ms (1.9x) 10.6 ms (2.0x) 13.4 ms (2.0x)
sumValues() 8.0 ms (2.7x) 7.7 ms (2.3x) 7.7 ms (2.8x) 9.1 ms (2.3x) 6.0 ms (2.5x) 8.1 ms (2.7x)
populationCount() 0.00 ms (251010x) 0.00 ms (181013x) 0.00 ms (241544x) 0.00 ms (224923x) 0.00 ms (163318x) 0.00 ms (248343x)
deleteRange() 39.1 ms (1.5x) 33.4 ms (1.5x) 37.2 ms (1.5x) 44.4 ms (1.4x) 34.3 ms (1.4x) 39.5 ms (1.4x)
equals() 9.8 ms (4.8x) 10.7 ms (4.0x) 9.9 ms (4.5x) 12.4 ms (3.9x) 9.5 ms (4.3x) 11.4 ms (4.2x)
union() Bitset 30.5 ms (0.2x) 27.9 ms (0.2x) 32.4 ms (0.1x) 31.5 ms (0.2x) 25.4 ms (0.2x) 30.8 ms (0.1x)
intersect() Bitset 12.9 ms (0.4x) 13.5 ms (0.5x) 13.1 ms (0.4x) 13.2 ms (0.4x) 11.8 ms (0.5x) 13.2 ms (0.4x)
diff() Bitset 13.3 ms (0.3x) 13.4 ms (0.2x) 12.8 ms (0.2x) 12.9 ms (0.3x) 11.7 ms (0.3x) 13.4 ms (0.2x)
xor() Bitset 25.7 ms (0.6x) 27.2 ms (0.5x) 26.0 ms (0.4x) 26.2 ms (0.4x) 23.3 ms (0.6x) 24.9 ms (0.4x)
mergeWith() Int→Int 51.2 ms (1.3x) 44.4 ms (1.3x) 51.4 ms (1.3x) 50.1 ms (1.3x) 43.3 ms (1.3x) 51.0 ms (1.3x)
mergeWith() Str→Int 231.0 ms (1.1x) 212.4 ms (1.1x) 230.3 ms (1.1x) 236.5 ms (1.1x) 233.0 ms (1.1x) 235.7 ms (1.1x)
forEach() Int→Int 26.2 ms (0.9x) 27.4 ms (0.7x) 25.6 ms (0.9x) 29.0 ms (0.8x) 24.3 ms (0.7x) 28.8 ms (0.9x)
filter() Int→Int 41.5 ms (~1.0x) 39.9 ms (0.8x) 40.3 ms (0.9x) 44.9 ms (0.9x) 38.8 ms (0.8x) 43.7 ms (0.9x)
map() Int→Int 45.2 ms (~1.0x) 42.5 ms (0.9x) 44.8 ms (~1.0x) 50.8 ms (~1.0x) 42.4 ms (0.9x) 48.2 ms (~1.0x)
forEach() Str→Int 64.0 ms (0.9x) 61.0 ms (0.9x) 64.3 ms (~1.0x) 63.9 ms (0.9x) 61.0 ms (0.9x) 65.3 ms (0.9x)
filter() Str→Int 116.2 ms (~1.0x) 115.8 ms (~1.0x) 116.0 ms (~1.0x) 122.1 ms (0.9x) 120.3 ms (0.9x) 120.4 ms (~1.0x)
map() Str→Int 154.7 ms (~1.0x) 156.1 ms (~1.0x) 156.9 ms (1.1x) 161.0 ms (~1.0x) 160.6 ms (~1.0x) 156.4 ms (~1.0x)
Cross-version detail (Windows)

Core Types — Windows

Each cell: Judy ms (speedup vs PHP array).

Type Op PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5 PHP 8.6
Bitset Write 21.0 ms (0.7x) 22.6 ms (0.5x) 22.4 ms (0.5x) 22.0 ms (0.5x) 23.4 ms (0.6x) 23.6 ms (0.6x)
Bitset Read 16.9 ms (0.4x) 17.2 ms (0.5x) 17.6 ms (0.4x) 17.8 ms (0.4x) 18.9 ms (0.5x) 17.8 ms (0.5x)
Int to Int Write 34.1 ms (0.4x) 35.4 ms (0.3x) 37.7 ms (0.3x) 35.6 ms (0.3x) 37.2 ms (0.3x) 38.3 ms (0.4x)
Int to Int Read 18.1 ms (0.4x) 18.1 ms (0.4x) 18.9 ms (0.4x) 18.7 ms (0.4x) 20.4 ms (0.5x) 18.6 ms (0.5x)
Int to Mixed Write 48.2 ms (0.5x) 46.3 ms (0.4x) 47.4 ms (0.4x) 51.1 ms (0.3x) 52.8 ms (0.3x) 54.5 ms (0.3x)
Int to Mixed Read 20.8 ms (0.5x) 20.4 ms (0.5x) 22.6 ms (0.5x) 19.8 ms (0.5x) 20.8 ms (0.5x) 19.4 ms (0.5x)
Int to Packed Write 66.7 ms (0.3x) 60.2 ms (0.3x) 67.2 ms (0.3x) 68.0 ms (0.2x) 74.1 ms (0.2x) 71.9 ms (0.2x)
Int to Packed Read 47.1 ms (0.2x) 46.5 ms (0.2x) 49.7 ms (0.2x) 48.2 ms (0.2x) 48.5 ms (0.2x) 50.9 ms (0.2x)
String to Int Write 143.2 ms (0.2x) 137.4 ms (0.2x) 188.4 ms (0.2x) 141.6 ms (0.2x) 145.0 ms (0.2x) 152.7 ms (0.2x)
String to Int Read 59.6 ms (0.2x) 55.9 ms (0.2x) 90.5 ms (0.2x) 63.1 ms (0.2x) 62.2 ms (0.2x) 65.5 ms (0.2x)
String to Mixed Write 185.1 ms (0.1x) 180.0 ms (0.2x) 184.9 ms (0.2x) 191.2 ms (0.2x) 184.6 ms (0.2x) 207.4 ms (0.1x)
String to Mixed Read 62.5 ms (0.2x) 60.3 ms (0.2x) 64.1 ms (0.2x) 63.7 ms (0.2x) 64.1 ms (0.2x) 68.3 ms (0.2x)
String to Int (Hash) Write 250.7 ms (0.1x) 241.1 ms (0.1x) 250.2 ms (0.1x) 246.1 ms (0.1x) 250.3 ms (0.1x) 275.1 ms (0.1x)
String to Int (Hash) Read 44.1 ms (0.3x) 43.1 ms (0.3x) 48.3 ms (0.3x) 44.2 ms (0.3x) 45.5 ms (0.3x) 50.2 ms (0.2x)
String to Mixed (Hash) Write 350.7 ms (0.1x) 339.0 ms (0.1x) 344.0 ms (0.1x) 350.4 ms (0.1x) 351.8 ms (0.1x) 398.9 ms (0.1x)
String to Mixed (Hash) Read 47.3 ms (0.3x) 46.9 ms (0.3x) 47.7 ms (0.3x) 47.7 ms (0.3x) 47.2 ms (0.3x) 53.7 ms (0.3x)
String to Int (Adaptive) Write 246.7 ms (0.1x) 242.0 ms (0.1x) 246.9 ms (0.1x) 244.3 ms (0.1x) 244.8 ms (0.1x) 266.9 ms (0.1x)
String to Int (Adaptive) Read 44.0 ms (0.3x) 44.6 ms (0.3x) 44.4 ms (0.3x) 45.6 ms (0.3x) 44.6 ms (0.3x) 50.3 ms (0.2x)
String to Mixed (Adaptive) Write 376.5 ms (0.1x) 360.3 ms (0.1x) 364.5 ms (0.1x) 377.6 ms (0.1x) 373.9 ms (0.1x) 444.2 ms (0.1x)
String to Mixed (Adaptive) Read 46.4 ms (0.3x) 46.9 ms (0.3x) 47.2 ms (0.3x) 47.6 ms (0.3x) 47.5 ms (0.3x) 59.4 ms (0.3x)

API — Windows

Operation PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5 PHP 8.6
putAll() 31.6 ms (1.5x) 30.4 ms (1.5x) 31.8 ms (1.5x) 31.1 ms (1.5x) 31.6 ms (1.5x) 40.8 ms (1.2x)
fromArray() 31.9 ms (1.5x) 30.3 ms (1.5x) 31.5 ms (1.5x) 30.3 ms (1.5x) 31.5 ms (1.5x) 35.7 ms (1.3x)
toArray() 28.1 ms (1.7x) 27.8 ms (1.8x) 28.8 ms (1.8x) 28.9 ms (1.7x) 27.4 ms (1.8x) 30.1 ms (1.6x)
getAll() 1.4 ms (2.2x) 1.4 ms (2.4x) 1.4 ms (2.3x) 1.6 ms (2.0x) 1.6 ms (2.0x) 2.3 ms (1.3x)
keys() 16.2 ms (2.2x) 12.5 ms (2.6x) 13.4 ms (2.6x) 12.7 ms (2.8x) 13.1 ms (2.5x) 15.1 ms (2.5x)
values() 16.7 ms (2.2x) 13.0 ms (2.5x) 14.1 ms (2.4x) 13.1 ms (2.7x) 13.5 ms (2.4x) 16.2 ms (2.4x)
sumValues() 7.8 ms (3.4x) 7.7 ms (3.4x) 7.8 ms (3.3x) 7.2 ms (3.7x) 8.4 ms (3.2x) 10.6 ms (3.1x)
populationCount() 0.00 ms (276390x) 0.00 ms (133418x) 0.00 ms (279921x) 0.00 ms (263383x) 0.00 ms (299698x) 0.00 ms (340835x)
deleteRange() 54.4 ms (1.5x) 54.5 ms (1.5x) 55.1 ms (1.5x) 55.2 ms (1.5x) 59.0 ms (1.4x) 70.5 ms (1.2x)
equals() 12.2 ms (5.0x) 11.3 ms (5.9x) 11.8 ms (5.4x) 10.7 ms (6.1x) 11.7 ms (5.9x) 12.2 ms (4.8x)
union() Bitset 34.1 ms (0.4x) 35.8 ms (0.2x) 34.5 ms (0.3x) 36.8 ms (0.2x) 36.4 ms (0.2x) 39.4 ms (0.2x)
intersect() Bitset 15.9 ms (0.5x) 16.6 ms (0.5x) 16.3 ms (0.4x) 18.0 ms (0.4x) 17.1 ms (0.5x) 17.2 ms (0.5x)
diff() Bitset 15.8 ms (0.4x) 16.5 ms (0.3x) 15.5 ms (0.3x) 17.3 ms (0.2x) 16.9 ms (0.2x) 17.1 ms (0.3x)
xor() Bitset 32.1 ms (0.8x) 33.7 ms (0.6x) 31.8 ms (0.6x) 35.0 ms (0.5x) 33.4 ms (0.5x) 34.5 ms (0.5x)
mergeWith() Int→Int 64.8 ms (1.3x) 67.5 ms (1.3x) 66.7 ms (1.4x) 66.2 ms (1.4x) 67.0 ms (1.3x) 69.7 ms (1.3x)
mergeWith() Str→Int 325.9 ms (1.1x) 306.9 ms (1.1x) 313.9 ms (1.1x) 335.8 ms (1.1x) 334.6 ms (1.1x) 352.4 ms (1.1x)
forEach() Int→Int 31.2 ms (0.9x) 35.2 ms (0.8x) 33.7 ms (0.9x) 33.1 ms (0.9x) 32.9 ms (~1.0x) 30.2 ms (~1.0x)
filter() Int→Int 47.8 ms (~1.0x) 51.8 ms (~1.0x) 44.9 ms (1.1x) 53.2 ms (0.9x) 54.5 ms (0.9x) 47.0 ms (~1.0x)
map() Int→Int 55.4 ms (1.1x) 54.0 ms (1.1x) 55.6 ms (1.1x) 61.3 ms (~1.0x) 60.5 ms (~1.0x) 55.3 ms (1.1x)
forEach() Str→Int 76.7 ms (~1.0x) 81.8 ms (0.9x) 80.3 ms (~1.0x) 84.8 ms (0.9x) 83.1 ms (~1.0x) 87.6 ms (~1.0x)
filter() Str→Int 157.3 ms (~1.0x) 152.1 ms (~1.0x) 151.5 ms (~1.0x) 157.5 ms (~1.0x) 161.0 ms (~1.0x) 164.5 ms (~1.0x)
map() Str→Int 220.3 ms (~1.0x) 206.1 ms (~1.0x) 217.1 ms (~1.0x) 219.4 ms (~1.0x) 225.6 ms (~1.0x) 232.1 ms (~1.0x)
Windows results (PHP 8.6)

Memory (Windows, PHP 8.6)

Peak RSS of a child process building one structure, n = 500,000, minus an empty-process floor. Judy's memory advantage is type- and scale-dependent — see BENCHMARK.md for the curve across sizes on a dedicated host.

Type Judy PHP array Judy vs PHP array
Bitset 140.0 KB 9.2 MB 67.6x smaller
Int to Int 4.4 MB 9.2 MB 2.1x smaller
Int to Mixed 42.6 MB 38.2 MB 1.11x larger
Int to Packed 17.2 MB 38.2 MB 2.2x smaller
String to Int 10.5 MB 38.0 MB 3.6x smaller
String to Mixed 48.7 MB 69.1 MB 1.4x smaller
String to Int (Hash) 32.1 MB 38.0 MB 1.2x smaller
String to Mixed (Hash) 70.3 MB 69.0 MB about the same
String to Int (Adaptive) 32.2 MB 38.0 MB 1.2x smaller
String to Mixed (Adaptive) 70.4 MB 69.0 MB about the same

API Speedups (Windows, PHP 8.6)

Speedup = PHP time / Judy time. Bold = Judy faster.

Operation Judy PHP Speedup
putAll() 40.8 ms 47.3 ms 1.2x
fromArray() 35.7 ms 47.3 ms 1.3x
toArray() 30.1 ms 48.9 ms 1.6x
getAll() 2.3 ms 2.9 ms 1.3x
keys() 15.1 ms 37.2 ms 2.5x
values() 16.2 ms 38.2 ms 2.4x
sumValues() 10.6 ms 33.5 ms 3.1x
populationCount() 0.00 ms 34.1 ms O(1) vs O(n)
deleteRange() 70.5 ms 86.3 ms 1.2x
equals() 12.2 ms 58.7 ms 4.8x
union() Bitset 39.4 ms 8.3 ms 0.2x
intersect() Bitset 17.2 ms 8.4 ms 0.5x
diff() Bitset 17.1 ms 4.3 ms 0.3x
xor() Bitset 34.5 ms 17.8 ms 0.5x
mergeWith() Int→Int 69.7 ms 90.4 ms 1.3x
mergeWith() Str→Int 352.4 ms 377.1 ms 1.1x
forEach() Int→Int 30.2 ms 29.4 ms ~1.0x
filter() Int→Int 47.0 ms 49.2 ms ~1.0x
map() Int→Int 55.3 ms 60.3 ms 1.1x
forEach() Str→Int 87.6 ms 85.4 ms ~1.0x
filter() Str→Int 164.5 ms 162.3 ms ~1.0x
map() Str→Int 232.1 ms 233.6 ms ~1.0x

O(1) vs O(n) marks a row whose ratio is decided by the complexity classes rather than measured: populationCount() reads libJudy's cached population, the PHP arm counts every element. It scales with n and says nothing about this change. judy-bench.php declines the same comparison for size($lo, $hi) for the same reason.

Core Types (Windows, PHP 8.6)

Speedup = PHP time / Judy time. Bold = Judy faster.

Type Op Judy PHP array Speedup
Bitset Write 23.6 ms 13.8 ms 0.6x
Bitset Read 17.8 ms 9.6 ms 0.5x
Bitset Iter 22.7 ms 6.9 ms 0.3x
Bitset Free 0.01 ms 0.57 ms 68.9x
Int to Int Write 38.3 ms 13.5 ms 0.4x
Int to Int Read 18.6 ms 9.4 ms 0.5x
Int to Int Iter 24.6 ms 6.9 ms 0.3x
Int to Int Free 0.72 ms 0.59 ms 0.8x
Int to Mixed Write 54.5 ms 16.9 ms 0.3x
Int to Mixed Read 19.4 ms 10.2 ms 0.5x
Int to Mixed Iter 26.8 ms 7.7 ms 0.3x
Int to Mixed Free 22.1 ms 6.5 ms 0.3x
String to Int Write 152.7 ms 25.0 ms 0.2x
String to Int Read 65.5 ms 11.9 ms 0.2x
String to Int Iter 80.9 ms 7.5 ms 0.1x
String to Int Free 34.9 ms 1.7 ms 0.0x
String to Int (Adaptive) Write 266.9 ms 25.3 ms 0.1x
String to Int (Adaptive) Read 50.3 ms 12.1 ms 0.2x
String to Int (Adaptive) Iter 125.0 ms 7.4 ms 0.1x
String to Int (Adaptive) Free 69.9 ms 1.7 ms 0.0x
All types detail

Core Types (Windows, PHP 8.6) — All

Speedup = PHP time / Judy time. Bold = Judy faster.

Type Op Judy PHP array Speedup
Bitset Write 23.6 ms 13.8 ms 0.6x
Bitset Read 17.8 ms 9.6 ms 0.5x
Bitset Iter 22.7 ms 6.9 ms 0.3x
Bitset Free 0.01 ms 0.57 ms 68.9x
Int to Int Write 38.3 ms 13.5 ms 0.4x
Int to Int Read 18.6 ms 9.4 ms 0.5x
Int to Int Iter 24.6 ms 6.9 ms 0.3x
Int to Int Free 0.72 ms 0.59 ms 0.8x
Int to Mixed Write 54.5 ms 16.9 ms 0.3x
Int to Mixed Read 19.4 ms 10.2 ms 0.5x
Int to Mixed Iter 26.8 ms 7.7 ms 0.3x
Int to Mixed Free 22.1 ms 6.5 ms 0.3x
Int to Packed Write 71.9 ms 16.5 ms 0.2x
Int to Packed Read 50.9 ms 9.9 ms 0.2x
Int to Packed Iter 56.5 ms 7.3 ms 0.1x
Int to Packed Free 15.8 ms 6.3 ms 0.4x
String to Int Write 152.7 ms 25.0 ms 0.2x
String to Int Read 65.5 ms 11.9 ms 0.2x
String to Int Iter 80.9 ms 7.5 ms 0.1x
String to Int Free 34.9 ms 1.7 ms 0.0x
String to Mixed Write 207.4 ms 29.6 ms 0.1x
String to Mixed Read 68.3 ms 15.0 ms 0.2x
String to Mixed Iter 97.9 ms 9.0 ms 0.1x
String to Mixed Free 114.0 ms 9.1 ms 0.1x
String to Int (Hash) Write 275.1 ms 24.9 ms 0.1x
String to Int (Hash) Read 50.2 ms 12.1 ms 0.2x
String to Int (Hash) Iter 126.7 ms 7.4 ms 0.1x
String to Int (Hash) Free 71.1 ms 1.7 ms 0.0x
String to Mixed (Hash) Write 398.9 ms 29.3 ms 0.1x
String to Mixed (Hash) Read 53.7 ms 14.5 ms 0.3x
String to Mixed (Hash) Iter 160.7 ms 9.1 ms 0.1x
String to Mixed (Hash) Free 217.0 ms 9.1 ms 0.0x
String to Int (Adaptive) Write 266.9 ms 25.3 ms 0.1x
String to Int (Adaptive) Read 50.3 ms 12.1 ms 0.2x
String to Int (Adaptive) Iter 125.0 ms 7.4 ms 0.1x
String to Int (Adaptive) Free 69.9 ms 1.7 ms 0.0x
String to Mixed (Adaptive) Write 444.2 ms 30.3 ms 0.1x
String to Mixed (Adaptive) Read 59.4 ms 15.7 ms 0.3x
String to Mixed (Adaptive) Iter 170.9 ms 10.1 ms 0.1x
String to Mixed (Adaptive) Free 234.8 ms 9.2 ms 0.0x

Release Comparison (2.6.0 → 2.7.1)

Interleaved A/B comparison against v2.6.0 (pie install) on the same CI runner: 5 benchmark groups × 5 rounds × 2 arms, 300,000 elements, median of 3 per child. Each row is the median of the paired current/baseline ratios with a 95% percentile-bootstrap CI; it is flagged only when the whole drift-adjusted CI clears ±10%. Advisory — benchmarks never gate a merge.

Run-wide median delta +0.88% (PHP-array control +0.28%); runner common-mode +0.28% divided out of every row.

Summary: ⚠️ 3 regressions, 97 unchanged

Benchmark Baseline Current Delta 95% CI Status
adv.filter.int_to_int 27.5 ms 27.8 ms +1.0% [+0.3%, +2.6%] ~same
adv.filter.string_to_int 71.0 ms 72.1 ms +1.4% [+0.1%, +2.3%] ~same
adv.forEach.int_to_int 16.5 ms 17.3 ms +5.4% [+1.8%, +9.1%] ~same
adv.forEach.string_to_int 38.6 ms 37.9 ms -2.3% [-3.2%, -1.9%] ~same
adv.map.int_to_int 29.6 ms 30.2 ms +1.5% [+0.8%, +2.5%] ~same
adv.map.string_to_int 93.6 ms 94.9 ms +1.2% [+0.1%, +1.9%] ~same
adv.optiter.foreach.string_to_int_adaptive.default 62.8 ms 62.9 ms +0.5% [-6.4%, +7.5%] ~same
adv.optiter.foreach.string_to_int_adaptive.optimized 41.8 ms 41.9 ms -0.3% [-3.9%, +3.4%] ~same
adv.optiter.foreach.string_to_int_hash.default 54.8 ms 52.7 ms -7.0% [-8.3%, +5.8%] ~same
adv.optiter.foreach.string_to_int_hash.optimized 32.4 ms 32.7 ms +0.4% [-1.4%, +1.2%] ~same
adv.optiter.increment.string_to_int_hash.default 37.3 ms 38.1 ms +1.9% [-8.8%, +5.4%] ~same
adv.optiter.increment.string_to_int_hash.optimized 42.6 ms 41.8 ms -2.6% [-8.6%, +7.0%] ~same
adv.optiter.overwrite.string_to_int_adaptive.default 54.3 ms 53.1 ms -2.5% [-6.9%, +8.4%] ~same
adv.optiter.overwrite.string_to_int_adaptive.optimized 49.2 ms 50.8 ms +2.9% [-4.2%, +4.6%] ~same
adv.optiter.overwrite.string_to_int_hash.default 38.3 ms 37.3 ms -2.8% [-22.5%, +1.3%] ~same
adv.optiter.overwrite.string_to_int_hash.optimized 42.9 ms 41.0 ms -3.4% [-9.3%, -2.4%] ~same
adv.optiter.ratio.foreach.string_to_int_adaptive 66.8 ms 66.7 ms -1.3% [-4.1%, +2.4%] ~same
adv.optiter.ratio.foreach.string_to_int_hash 60.0 ms 61.7 ms +7.1% [-4.6%, +8.4%] ~same
adv.optiter.ratio.increment.string_to_int_hash 110.6 ms 110.6 ms -0.1% [-4.7%, +1.4%] ~same
adv.optiter.ratio.overwrite.string_to_int_adaptive 90.7 ms 94.9 ms +3.0% [-3.8%, +6.6%] ~same
adv.optiter.ratio.overwrite.string_to_int_hash 111.3 ms 109.1 ms -1.0% [-6.1%, +16.6%] ~same
adv.optiter.ratio.toArray.string_to_int_adaptive 62.8 ms 63.2 ms +1.6% [-2.4%, +6.6%] ~same
adv.optiter.ratio.toArray.string_to_int_hash 55.8 ms 55.6 ms +2.3% [-7.0%, +8.0%] ~same
adv.optiter.ratio.values.string_to_int_adaptive 59.4 ms 59.1 ms -1.1% [-3.4%, +2.6%] ~same
adv.optiter.ratio.values.string_to_int_hash 49.6 ms 51.8 ms +4.1% [-3.7%, +6.4%] ~same
adv.optiter.toArray.string_to_int_adaptive.default 69.7 ms 69.2 ms -0.7% [-6.4%, +4.1%] ~same
adv.optiter.toArray.string_to_int_adaptive.optimized 44.1 ms 44.3 ms +0.1% [-2.9%, +6.1%] ~same
adv.optiter.toArray.string_to_int_hash.default 58.4 ms 57.2 ms -3.7% [-9.3%, +3.9%] ~same
adv.optiter.toArray.string_to_int_hash.optimized 32.6 ms 32.0 ms -2.3% [-5.0%, -1.2%] ~same
adv.optiter.values.string_to_int_adaptive.default 55.4 ms 55.3 ms -0.7% [-6.0%, +3.4%] ~same
adv.optiter.values.string_to_int_adaptive.optimized 32.7 ms 32.3 ms -2.3% [-8.0%, +6.4%] ~same
adv.optiter.values.string_to_int_hash.default 49.1 ms 45.9 ms -6.7% [-7.3%, +0.6%] ~same
adv.optiter.values.string_to_int_hash.optimized 24.3 ms 23.8 ms -2.6% [-3.2%, -0.8%] ~same
api.deleteRange.int_to_int 23.4 ms 26.7 ms +13.2% [+12.2%, +13.9%] ⚠️ SLOWER
api.equals.int_to_int 7.1 ms 7.4 ms +2.3% [-1.7%, +12.8%] ~same
api.fromArray.int_to_int 13.8 ms 13.4 ms -4.8% [-7.1%, -3.7%] ~same
api.getAll.int_to_int 0.53 ms 0.53 ms +0.4% [-8.9%, +1.9%] ~same
api.increment.int_to_int 14.5 ms 14.8 ms +1.5% [-0.6%, +2.5%] ~same
api.keys.int_to_int 6.5 ms 7.0 ms +8.3% [+5.7%, +9.2%] ~same
api.mergeWith.int_to_int 31.0 ms 30.5 ms -2.0% [-3.8%, +4.4%] ~same
api.mergeWith.string_to_int 135.5 ms 138.6 ms +2.0% [+1.1%, +5.3%] ~same
api.populationCount.int_to_int 0.00 ms 0.00 ms -0.3% [-0.3%, -0.3%] ~same
api.putAll.int_to_int 14.0 ms 13.4 ms -4.8% [-7.0%, -3.7%] ~same
api.range.keys.int_to_int 0.58 ms 0.63 ms +11.0% [+9.6%, +12.4%] ~same
api.range.size.string_to_int 2.3 ms 2.3 ms -1.1% [-1.8%, -0.2%] ~same
api.range.size1000.int_to_int 0.72 ms 0.67 ms -7.8% [-8.8%, -6.8%] ~same
api.setop.diff.bitset 7.8 ms 7.7 ms -1.3% [-2.2%, -0.8%] ~same
api.setop.diff.int_to_int 10.1 ms 10.7 ms +6.2% [+2.8%, +8.9%] ~same
api.setop.diff.string_to_int 63.2 ms 65.2 ms +2.6% [+0.6%, +3.2%] ~same
api.setop.intersect.bitset 7.7 ms 7.8 ms +0.5% [-0.2%, +1.5%] ~same
api.setop.intersect.int_to_int 10.2 ms 10.9 ms +6.6% [+3.7%, +9.9%] ~same
api.setop.intersect.string_to_int 71.0 ms 72.4 ms +2.1% [+1.3%, +2.5%] ~same
api.setop.union.bitset 18.3 ms 19.0 ms +3.1% [+0.8%, +3.9%] ~same
api.setop.union.int_to_int 26.6 ms 26.7 ms -0.8% [-1.4%, +5.5%] ~same
api.setop.union.string_to_int 158.3 ms 162.6 ms +2.6% [+1.7%, +4.0%] ~same
api.setop.xor.bitset 15.5 ms 15.6 ms +0.6% [-0.3%, +1.9%] ~same
api.setop.xor.int_to_int 20.8 ms 21.7 ms +4.3% [+0.7%, +6.7%] ~same
api.sumValues.int_to_int 4.4 ms 5.0 ms +12.8% [+5.3%, +23.6%] ~same
api.toArray.int_to_int 15.0 ms 17.5 ms +15.9% [+14.7%, +19.1%] ⚠️ SLOWER
api.values.int_to_int 6.7 ms 7.6 ms +12.4% [+7.5%, +17.2%] ~same
core.bitset.free 0.00 ms 0.01 ms +14.3% [+10.8%, +38.6%] ~same
core.bitset.iter 10.7 ms 11.2 ms +2.2% [-2.9%, +6.7%] ~same
core.bitset.read 6.6 ms 6.5 ms -1.6% [-2.0%, +4.1%] ~same
core.bitset.write 10.3 ms 10.1 ms +0.2% [-1.9%, +1.9%] ~same
core.int_to_int.free 0.35 ms 0.36 ms +13.0% [-36.4%, +66.0%] ~same
core.int_to_int.iter 11.5 ms 12.5 ms +8.7% [-1.1%, +10.9%] ~same
core.int_to_int.read 6.8 ms 6.8 ms +2.0% [-14.8%, +3.6%] ~same
core.int_to_int.write 14.5 ms 13.9 ms -4.1% [-4.8%, -2.1%] ~same
core.int_to_mixed.free 8.0 ms 9.0 ms +12.1% [+9.0%, +13.8%] ~same
core.int_to_mixed.iter 12.6 ms 13.3 ms +5.5% [+3.2%, +8.3%] ~same
core.int_to_mixed.read 8.4 ms 8.5 ms +1.1% [-3.6%, +20.6%] ~same
core.int_to_mixed.write 20.3 ms 20.4 ms +0.8% [-4.2%, +2.5%] ~same
core.int_to_packed.free 5.3 ms 5.9 ms +12.0% [+11.1%, +12.8%] ⚠️ SLOWER
core.int_to_packed.iter 22.8 ms 23.5 ms +2.5% [+1.6%, +4.2%] ~same
core.int_to_packed.read 18.4 ms 18.2 ms -1.6% [-2.0%, +1.1%] ~same
core.int_to_packed.write 27.8 ms 28.0 ms +1.0% [+0.2%, +2.2%] ~same
core.string_to_int.free 10.1 ms 10.1 ms -0.1% [-0.5%, +2.2%] ~same
core.string_to_int.iter 32.8 ms 32.6 ms -0.4% [-2.5%, +0.7%] ~same
core.string_to_int.read 21.3 ms 21.5 ms +0.6% [-0.4%, +0.9%] ~same
core.string_to_int.write 51.5 ms 51.9 ms +0.7% [-0.4%, +5.2%] ~same
core.string_to_int_adaptive.free 22.5 ms 22.7 ms +1.0% [-1.4%, +8.0%] ~same
core.string_to_int_adaptive.iter 39.6 ms 39.1 ms -1.6% [-2.5%, -1.2%] ~same
core.string_to_int_adaptive.read 14.5 ms 14.6 ms +1.1% [-1.2%, +2.0%] ~same
core.string_to_int_adaptive.write 90.6 ms 92.5 ms +1.9% [+1.1%, +7.4%] ~same
core.string_to_int_hash.free 22.2 ms 23.1 ms +2.0% [-1.3%, +10.3%] ~same
core.string_to_int_hash.iter 40.2 ms 39.7 ms -1.4% [-2.9%, -0.2%] ~same
core.string_to_int_hash.read 14.9 ms 15.0 ms +0.8% [-4.2%, +1.7%] ~same
core.string_to_int_hash.write 93.1 ms 93.2 ms -0.7% [-1.7%, +2.5%] ~same
core.string_to_mixed.free 34.7 ms 34.6 ms +0.3% [-2.6%, +2.7%] ~same
core.string_to_mixed.iter 36.7 ms 36.8 ms -0.2% [-3.0%, +0.5%] ~same
core.string_to_mixed.read 23.6 ms 23.8 ms -0.1% [-0.6%, +0.7%] ~same
core.string_to_mixed.write 65.0 ms 65.5 ms +0.9% [-0.5%, +5.4%] ~same
core.string_to_mixed_adaptive.free 65.5 ms 67.4 ms +3.0% [-0.3%, +5.3%] ~same
core.string_to_mixed_adaptive.iter 48.6 ms 48.0 ms -1.7% [-1.9%, +3.3%] ~same
core.string_to_mixed_adaptive.read 16.3 ms 16.2 ms -0.2% [-3.2%, +1.5%] ~same
core.string_to_mixed_adaptive.write 133.7 ms 132.6 ms -0.9% [-1.5%, +2.5%] ~same
core.string_to_mixed_hash.free 64.2 ms 64.8 ms +0.2% [-0.1%, +2.6%] ~same
core.string_to_mixed_hash.iter 50.9 ms 50.8 ms -0.5% [-1.9%, +0.1%] ~same
core.string_to_mixed_hash.read 16.4 ms 16.8 ms +2.6% [-1.3%, +4.2%] ~same
core.string_to_mixed_hash.write 127.9 ms 128.5 ms +0.1% [-0.4%, +1.6%] ~same

Memory Comparison

Memory: 0 regressions, 10 unchanged

Type Baseline Current Delta Status
Bitset 160 B 160 B 0.0% ~same
Int to Int 160 B 160 B 0.0% ~same
Int to Mixed 4.6 MB 4.6 MB 0.0% ~same
Int to Packed 8.0 MB 8.0 MB 0.0% ~same
String to Int 64.2 KB 64.2 KB 0.0% ~same
String to Int (Adaptive) 64.2 KB 64.2 KB 0.0% ~same
String to Int (Hash) 64.2 KB 64.2 KB 0.0% ~same
String to Mixed 4.6 MB 4.6 MB 0.0% ~same
String to Mixed (Adaptive) 4.6 MB 4.6 MB 0.0% ~same
String to Mixed (Hash) 4.6 MB 4.6 MB 0.0% ~same
Full benchmark output

Linux — PHP 8.6

══════════════════════════════════════════════════════════════════════════════════════════
  php-judy Benchmark Suite — 500,000 elements, 7 iterations (median)
  PHP 8.6.0-dev | Judy ext 2.7.1 | Linux x86_64
  Suite: all | Groups: core.int,core.str,api.batch,api.setops,adv.iter,adv.sso
  memory_limit: -1
  Memory: peak RSS of a child process, empty-process floor subtracted, median of 3
══════════════════════════════════════════════════════════════════════════════════════════

── Core: Integer-Keyed Types ────────────────────────────────────────────────

  Type                         Write(ms)      Read(ms)      Iter(ms)      Free(ms)        Memory
  ────────────────────────  ────────────  ────────────  ────────────  ────────────  ────────────
  BITSET                           20.31         12.91         20.44          0.01    < 528.0 KB
    PHP array                       8.88          5.28          3.71          0.37        8.0 MB
  INT_TO_INT                       24.71         11.98         22.72          0.44        4.3 MB
    PHP array                       7.95          5.47          3.70          0.37        8.2 MB
  INT_TO_MIXED                     36.26         14.16         22.66         16.58       40.4 MB
    PHP array                      12.43          6.69          3.98          5.00       38.0 MB
  INT_TO_PACKED                    48.24         29.57         38.44         10.09       16.3 MB
    PHP array                      11.58          6.56          4.02          5.39       38.1 MB

── Core: String-Keyed Types ────────────────────────────────────────────────

  Type                         Write(ms)      Read(ms)      Iter(ms)      Free(ms)        Memory
  ────────────────────────  ────────────  ────────────  ────────────  ────────────  ────────────
  STRING_TO_INT                    92.86         40.10         57.49         20.83       10.1 MB
    PHP array                      16.11          7.46          4.90          1.04       38.0 MB
  STRING_TO_MIXED                 117.87         43.87         72.21         70.74       46.2 MB
    PHP array                      20.08         10.19          6.40          7.65       68.1 MB
  STR_TO_INT_HASH                 165.36         27.24         72.06         46.96       31.5 MB
    PHP array                      16.00          7.67          4.85          1.03       38.1 MB
  STR_TO_MIX_HASH                 248.35         31.04         99.08        132.30       67.3 MB
    PHP array                      20.02         10.31          6.48          7.82       68.1 MB
  STR_INT_ADAPTIVE                167.21         26.58         71.10         46.52       31.5 MB
    PHP array                      16.02          7.46          4.86          1.14       38.1 MB
  STR_MIX_ADAPTIVE                267.23         30.23        111.73        136.70       67.4 MB
    PHP array                      20.32         10.35          6.64          7.66       68.1 MB

── API: Batch Operations (INT_TO_INT) ────────────────────────────────────────

  Operation                           Judy(ms)       PHP(ms)     Speedup
  ──────────────────────────────  ────────────  ────────────  ──────────
  putAll()                               20.43         29.22        1.4x
  fromArray()                            20.45         29.22        1.4x
  toArray()                              26.35         40.83        1.5x
  getAll(50100 keys)                      1.12          2.12        1.9x
  increment(500000 ops/1000 keys)         26.39         12.53        0.5x

── API: Native Methods (keys, values, sum, popCount, deleteRange, equals) ────

  Operation                           Judy(ms)       PHP(ms)     Speedup
  ──────────────────────────────  ────────────  ────────────  ──────────
  keys() INT_TO_INT                      12.39         27.42        2.2x
  values() INT_TO_INT                    13.41         27.29        2.0x
  keys($lo,$hi) INT_TO_INT                1.09         27.53       25.2x
    vs slice()->keys()                    3.07             -        2.8x
  size($lo,$hi) INT x1000                 1.05             -        O(1)
  size($lo,$hi) STRING_TO_INT             4.12          5.32        1.3x
  sumValues() INT_TO_INT                  8.13         21.68        2.7x
  populationCount() INT_TO_INT            0.00         24.83   248343.1x
  deleteRange() INT_TO_INT mid50%         39.51         55.75        1.4x
  equals() INT_TO_INT                    11.43         48.45        4.2x

── API: Set Operations (BITSET, 50% overlap) ────────────────────────────────

  Operation                           Judy(ms)       PHP(ms)     Speedup
  ──────────────────────────────  ────────────  ────────────  ──────────
  union() BITSET                         30.76          4.26        0.1x
  intersect() BITSET                     13.16          5.42        0.4x
  diff() BITSET                          13.37          3.26        0.2x
  xor() BITSET                           24.87         10.97        0.4x
  union() INT_TO_INT                     43.68         89.38        2.0x
  intersect() INT_TO_INT                 17.72         42.88        2.4x
  diff() INT_TO_INT                      16.57         43.77        2.6x
  xor() INT_TO_INT                       35.15         88.34        2.5x
  union() STRING_TO_INT                 280.21        310.95        1.1x
  intersect() STRING_TO_INT             121.66        142.95        1.2x
  diff() STRING_TO_INT                  116.43        137.34        1.2x
  mergeWith() INT_TO_INT                 51.00         68.27        1.3x
  mergeWith() STRING_TO_INT             235.66        251.60        1.1x

── Advanced: C-Level forEach/filter/map ──────────────────────────────────────

  Operation                        C-level(ms)       PHP(ms)     Speedup
  ──────────────────────────────  ────────────  ────────────  ──────────
  forEach() INT_TO_INT                   28.83         26.10        0.9x
  forEach() STRING_TO_INT                65.29         61.04        0.9x
  filter() INT_TO_INT (50% pass)         43.65         40.89        0.9x
  filter() STRING_TO_INT (50% pass)        120.41        116.12        1.0x
  map() INT_TO_INT (*2)                  48.15         48.79        1.0x
  map() STRING_TO_INT (*2)              156.44        158.55        1.0x

── Advanced: optimizeIteration trade (opt-in key-index payload mirror) ──────

  Operation                        default(ms)    optim.(ms)   optim./def.
  ──────────────────────────────  ────────────  ────────────  ────────────
  foreach() HASH kl16                    74.43         35.23         47.3%
  values() HASH kl16                     60.73         26.82         44.2%
  toArray() HASH kl16                    73.03         34.94         47.8%
  overwrite() HASH kl16                  54.09         59.07        109.2%
  increment() HASH kl16                  55.66         58.17        104.5%
    HASH kl16: honoured?          yes
  foreach() ADAPTIVE kl40                80.74         46.56         57.7%
  values() ADAPTIVE kl40                 71.69         37.13         51.8%
  toArray() ADAPTIVE kl40                89.97         52.01         57.8%
  overwrite() ADAPTIVE kl40              69.82         73.60        105.4%
    ADAPTIVE kl40: honoured?      yes
  skipped — increment() ADAPTIVE kl40: Judy::increment() is only supported for INT_TO_INT, STRING_TO_INT and STRING_TO_INT_HASH types

  Ratios are optimized as a percentage of default, measured inside this
  build; below 100 is the traversal win, above 100 the write price. Lower
  is better on every row, so a lost win reads as a rise.

── Advanced: Adaptive SSO — short keys (<8 bytes) via JudyL ─────────────────

  Operation                   STR_TO_INT    STR_INT_HASH   STR_INT_ADPTV    Best ratio
  ──────────────────────  ──────────────  ──────────────  ──────────────  ────────────
  insert (short keys)          79.17 ms      128.99 ms      122.06 ms       1.6x
  read (random access)         32.08 ms       24.20 ms       25.21 ms       1.3x
  foreach (iterate all)        52.45 ms       98.24 ms      104.49 ms       1.9x

── Advanced: Adaptive SSO — long keys (>=8 bytes) fallback to JudyHS ────────

  Operation                   STR_TO_INT    STR_INT_HASH   STR_INT_ADPTV    Best ratio
  ──────────────────────  ──────────────  ──────────────  ──────────────  ────────────
  insert (long keys)          126.55 ms      223.46 ms      220.34 ms       1.8x
  read (random access)         49.51 ms       42.32 ms       44.13 ms       1.2x
  foreach (iterate all)        92.95 ms      101.92 ms      100.71 ms       1.1x

══════════════════════════════════════════════════════════════════════════════════════════
  Benchmark complete — 2026-08-26 06:17:12
  All timings: median of 7 iterations via hrtime(true), 1 warmup run
  Memory: peak RSS of a child process building one structure of 500,000 elements,
          minus an empty-process floor. Measured with getrusage(), not
          memory_get_usage(), for the reason above. Judy's memory advantage is
          scale- and type-dependent — see BENCHMARK.md for the curve.
══════════════════════════════════════════════════════════════════════════════════════════

  JSON results written to: bench.json

Windows — PHP 8.6

══════════════════════════════════════════════════════════════════════════════════════════
  php-judy Benchmark Suite — 500,000 elements, 7 iterations (median)
  PHP 8.6.0beta2 | Judy ext 2.7.1 | WINNT AMD64
  Suite: all | Groups: core.int,core.str,api.batch,api.setops,adv.iter,adv.sso
  memory_limit: 4G
  Memory: peak RSS of a child process, empty-process floor subtracted, median of 3
══════════════════════════════════════════════════════════════════════════════════════════

── Core: Integer-Keyed Types ────────────────────────────────────────────────

  Type                         Write(ms)      Read(ms)      Iter(ms)      Free(ms)        Memory
  ────────────────────────  ────────────  ────────────  ────────────  ────────────  ────────────
  BITSET                           23.57         17.83         22.74          0.01      140.0 KB
    PHP array                      13.76          9.57          6.92          0.57        9.2 MB
  INT_TO_INT                       38.28         18.58         24.58          0.72        4.4 MB
    PHP array                      13.47          9.37          6.92          0.59        9.2 MB
  INT_TO_MIXED                     54.48         19.42         26.77         22.09       42.6 MB
    PHP array                      16.86         10.18          7.68          6.48       38.2 MB
  INT_TO_PACKED                    71.90         50.94         56.48         15.75       17.2 MB
    PHP array                      16.45          9.95          7.28          6.26       38.2 MB

── Core: String-Keyed Types ────────────────────────────────────────────────

  Type                         Write(ms)      Read(ms)      Iter(ms)      Free(ms)        Memory
  ────────────────────────  ────────────  ────────────  ────────────  ────────────  ────────────
  STRING_TO_INT                   152.68         65.47         80.87         34.90       10.5 MB
    PHP array                      25.03         11.86          7.51          1.66       38.0 MB
  STRING_TO_MIXED                 207.40         68.29         97.86        114.00       48.7 MB
    PHP array                      29.57         15.05          9.02          9.09       69.1 MB
  STR_TO_INT_HASH                 275.10         50.25        126.70         71.10       32.1 MB
    PHP array                      24.94         12.10          7.39          1.66       38.0 MB
  STR_TO_MIX_HASH                 398.88         53.71        160.69        217.03       70.3 MB
    PHP array                      29.28         14.47          9.06          9.15       69.0 MB
  STR_INT_ADAPTIVE                266.88         50.34        125.03         69.86       32.2 MB
    PHP array                      25.26         12.05          7.37          1.66       38.0 MB
  STR_MIX_ADAPTIVE                444.20         59.37        170.90        234.78       70.4 MB
    PHP array                      30.30         15.68         10.06          9.17       69.0 MB

── API: Batch Operations (INT_TO_INT) ────────────────────────────────────────

  Operation                           Judy(ms)       PHP(ms)     Speedup
  ──────────────────────────────  ────────────  ────────────  ──────────
  putAll()                               40.80         47.32        1.2x
  fromArray()                            35.69         47.32        1.3x
  toArray()                              30.13         48.90        1.6x
  getAll(50100 keys)                      2.30          2.92        1.3x
  increment(500000 ops/1000 keys)         34.77         26.84        0.8x

── API: Native Methods (keys, values, sum, popCount, deleteRange, equals) ────

  Operation                           Judy(ms)       PHP(ms)     Speedup
  ──────────────────────────────  ────────────  ────────────  ──────────
  keys() INT_TO_INT                      15.10         37.22        2.5x
  values() INT_TO_INT                    16.21         38.20        2.4x
  keys($lo,$hi) INT_TO_INT                1.38         42.49       30.9x
    vs slice()->keys()                    3.83             -        2.8x
  size($lo,$hi) INT x1000                 0.85             -        O(1)
  size($lo,$hi) STRING_TO_INT             6.74          9.95        1.5x
  sumValues() INT_TO_INT                 10.64         33.45        3.1x
  populationCount() INT_TO_INT            0.00         34.08   340835.0x
  deleteRange() INT_TO_INT mid50%         70.53         86.26        1.2x
  equals() INT_TO_INT                    12.20         58.69        4.8x

── API: Set Operations (BITSET, 50% overlap) ────────────────────────────────

  Operation                           Judy(ms)       PHP(ms)     Speedup
  ──────────────────────────────  ────────────  ────────────  ──────────
  union() BITSET                         39.41          8.30        0.2x
  intersect() BITSET                     17.23          8.39        0.5x
  diff() BITSET                          17.13          4.32        0.3x
  xor() BITSET                           34.45         17.77        0.5x
  union() INT_TO_INT                     59.43        113.34        1.9x
  intersect() INT_TO_INT                 21.47         52.28        2.4x
  diff() INT_TO_INT                      21.81         56.75        2.6x
  xor() INT_TO_INT                       44.48        113.19        2.5x
  union() STRING_TO_INT                 405.49        448.57        1.1x
  intersect() STRING_TO_INT             183.41        208.02        1.1x
  diff() STRING_TO_INT                  169.04        195.73        1.2x
  mergeWith() INT_TO_INT                 69.71         90.38        1.3x
  mergeWith() STRING_TO_INT             352.45        377.08        1.1x

── Advanced: C-Level forEach/filter/map ──────────────────────────────────────

  Operation                        C-level(ms)       PHP(ms)     Speedup
  ──────────────────────────────  ────────────  ────────────  ──────────
  forEach() INT_TO_INT                   30.24         29.44        1.0x
  forEach() STRING_TO_INT                87.63         85.39        1.0x
  filter() INT_TO_INT (50% pass)         46.99         49.17        1.0x
  filter() STRING_TO_INT (50% pass)        164.54        162.31        1.0x
  map() INT_TO_INT (*2)                  55.28         60.35        1.1x
  map() STRING_TO_INT (*2)              232.12        233.57        1.0x

── Advanced: optimizeIteration trade (opt-in key-index payload mirror) ──────

  Operation                        default(ms)    optim.(ms)   optim./def.
  ──────────────────────────────  ────────────  ────────────  ────────────
  foreach() HASH kl16                    95.82         62.16         64.9%
  values() HASH kl16                     76.81         51.83         67.5%
  toArray() HASH kl16                   101.96         65.50         64.2%
  overwrite() HASH kl16                  68.06         72.13        106.0%
  increment() HASH kl16                  72.79         78.34        107.6%
    HASH kl16: honoured?          yes
  foreach() ADAPTIVE kl40               121.42         57.78         47.6%
  values() ADAPTIVE kl40                109.93         45.37         41.3%
  toArray() ADAPTIVE kl40               133.94         66.96         50.0%
  overwrite() ADAPTIVE kl40              86.18        100.28        116.4%
    ADAPTIVE kl40: honoured?      yes
  skipped — increment() ADAPTIVE kl40: Judy::increment() is only supported for INT_TO_INT, STRING_TO_INT and STRING_TO_INT_HASH types

  Ratios are optimized as a percentage of default, measured inside this
  build; below 100 is the traversal win, above 100 the write price. Lower
  is better on every row, so a lost win reads as a rise.

── Advanced: Adaptive SSO — short keys (<8 bytes) via JudyL ─────────────────

  Operation                   STR_TO_INT    STR_INT_HASH   STR_INT_ADPTV    Best ratio
  ──────────────────────  ──────────────  ──────────────  ──────────────  ────────────
  insert (short keys)         114.27 ms      197.13 ms      177.36 ms       1.7x
  read (random access)         30.62 ms       24.86 ms       21.93 ms       1.4x
  foreach (iterate all)        69.67 ms      124.43 ms      121.36 ms       1.8x

── Advanced: Adaptive SSO — long keys (>=8 bytes) fallback to JudyHS ────────

  Operation                   STR_TO_INT    STR_INT_HASH   STR_INT_ADPTV    Best ratio
  ──────────────────────  ──────────────  ──────────────  ──────────────  ────────────
  insert (long keys)          190.65 ms      341.90 ms      340.82 ms       1.8x
  read (random access)         46.88 ms       35.24 ms       38.19 ms       1.3x
  foreach (iterate all)       131.84 ms      196.50 ms      197.81 ms       1.5x

══════════════════════════════════════════════════════════════════════════════════════════
  Benchmark complete — 2026-08-26 06:18:36
  All timings: median of 7 iterations via hrtime(true), 1 warmup run
  Memory: peak RSS of a child process building one structure of 500,000 elements,
          minus an empty-process floor. Measured with getrusage(), not
          memory_get_usage(), for the reason above. Judy's memory advantage is
          scale- and type-dependent — see BENCHMARK.md for the curve.
══════════════════════════════════════════════════════════════════════════════════════════

  JSON results written to: bench.json

@orieg
orieg merged commit 99701b4 into main Aug 26, 2026
39 checks passed
@orieg
orieg deleted the claude/php-judy-issue-202-11b62c branch August 26, 2026 06:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🚨 [Nightly CI Failure] Valgrind Memory Leak Detected on main

1 participant