Skip to content

perf: overhaul foreach iterator — valid flag, heap key buffer, string reuse#54

Merged
orieg merged 1 commit intomainfrom
optimize/iterator-overhaul
Mar 1, 2026
Merged

perf: overhaul foreach iterator — valid flag, heap key buffer, string reuse#54
orieg merged 1 commit intomainfrom
optimize/iterator-overhaul

Conversation

@orieg
Copy link
Owner

@orieg orieg commented Mar 1, 2026

Summary

  • Adds a valid boolean field to judy_iterator — set by rewind/move_forward, read by valid() — eliminating a redundant Judy lookup per iteration step
  • Replaces the stack-allocated uint8_t key[PHP_JUDY_MAX_LENGTH] string buffer with a single heap allocation (key_scratch) reused across iterations, avoiding repeated stack-frame zeroing
  • Reuses existing zend_string for the current key across move_forward calls instead of destroying and re-allocating on every step

Test plan

  • All existing tests pass (including all foreach_*.phpt tests)
  • tests/foreach_string_key_stress_001.phpt — string key iterator under moderate load

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly optimizes the performance of Judy array iterators, particularly for string keys. By introducing a cached validity flag, reusing a heap-allocated buffer for key storage, and implementing zend_string reuse, it reduces overhead associated with redundant lookups, stack allocations, and string object management during iteration.

Highlights

  • Introduced valid flag: A zend_bool field valid was added to judy_iterator to cache the iteration state, eliminating redundant Judy array lookups in judy_iterator_valid.
  • Heap-allocated key buffer: The stack-allocated uint8_t key[PHP_JUDY_MAX_LENGTH] buffer for string keys was replaced with a single heap-allocated key_scratch buffer, reducing stack usage and avoiding repeated zeroing.
  • zend_string reuse: Logic was added to reuse the zend_string object for the current key (it->key) across iterations when possible, minimizing memory allocations and deallocations.
Changelog
  • judy_iterator.c
    • Initialized the new valid flag and allocated memory for key_scratch in the iterator constructor.
    • Reset the valid flag in the data destructor.
    • Freed the key_scratch buffer in the iterator destructor.
    • Refactored judy_iterator_valid to use the new valid flag, eliminating redundant Judy lookups.
    • Set the valid flag to true when an element is successfully found during iteration.
    • Switched from stack-allocated key buffer to the heap-allocated key_scratch for string key operations.
    • Optimized string key handling by reusing existing zend_string objects where possible, reducing allocations.
  • judy_iterator.h
    • Added valid flag and key_scratch buffer to the judy_iterator structure.
  • tests/foreach_string_key_stress_001.phpt
    • Added a new stress test for string key iteration with varying key lengths to verify performance and correctness.
Activity
  • No activity (comments, reviews, etc.) has been provided for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several performance optimizations for foreach iteration. A valid flag is added to the iterator to avoid redundant lookups, a stack-allocated buffer is replaced with a reused heap allocation to prevent large stack frames, and zend_string instances are reused to reduce allocation overhead. These changes are logical, well-implemented, and accompanied by a new stress test. The code appears correct and robust.

… reuse

Addresses the #1 performance bottleneck: foreach is 25-50x slower than
PHP arrays at 500K elements. Five compounding root causes are fixed:

1. Replace full Judy lookup in valid() with a cached boolean flag.
   The 65-line function (8 type-dispatched J1T/JLG/JSLG/JHSG lookups)
   is replaced by a single flag check. The flag is set in rewind()
   and move_forward(), cleared in judy_iterator_data_dtor().

2. Replace per-step 64KB stack buffers with a single heap allocation.
   move_forward() and rewind() each allocated uint8_t key[65536] on
   the stack for string-keyed types — 128KB of stack touched per
   iteration step. Now uses a single key_scratch buffer allocated
   once in judy_get_iterator() and freed in judy_iterator_dtor().

3. Reuse zend_string allocation across iteration steps. Instead of
   zval_ptr_dtor + ZVAL_STRING (free + strlen + alloc + copy) per
   step, reuse the existing zend_string buffer when the new key fits
   (refcount==1, not interned, new_len <= old_len). Falls back to
   fresh allocation when reuse isn't possible.

4. Use ZVAL_STRINGL instead of ZVAL_STRING to avoid redundant strlen
   calls where the length is already known.

5. Use pre-computed strlen for JHSG calls in hash-type iteration,
   avoiding a second strlen on the same buffer.

Adds a 200-key varying-length string key stress test to verify
iteration correctness across rewind, full traversal, and empty arrays.
@orieg orieg force-pushed the optimize/iterator-overhaul branch from 61cb6fe to b0b33f9 Compare March 1, 2026 19:03
@orieg
Copy link
Owner Author

orieg commented Mar 1, 2026

The rebase onto updated main also surfaced a latent bug that the review helped
confirm: it->valid = 1 was missing in both judy_iterator_move_forward() and
judy_iterator_rewind() for the TYPE_STRING_TO_MIXED_HASH /
TYPE_STRING_TO_INT_HASH branches introduced by the hash-type PRs. With the new
fast it->valid flag path in judy_iterator_valid(), those two types would
silently produce zero iterations in a foreach loop. Added the missing
assignments and covered it with the existing string_to_*_hash_004.phpt tests
(163/163 pass).

@github-actions
Copy link

github-actions bot commented Mar 1, 2026

Test Results

PHP Platform Arch TS Tests Pass Fail Skip Duration
8.1 Linux x64 - 163 163 0 0 1.3s
8.2 Linux x64 - 163 163 0 0 1.7s
8.3 Linux x64 - 163 163 0 0 1.7s
8.4 Linux x64 - 163 163 0 0 1.7s
8.5 Linux x64 - 163 163 0 0 1.9s
8.1 Windows x64 nts 163 163 0 0 7.8s
8.2 Windows x64 nts 163 163 0 0 7.8s
8.3 Windows x64 nts 163 163 0 0 7.7s
8.4 Windows x64 nts 163 163 0 0 7.8s
8.5 Windows x64 nts 163 163 0 0 8.6s
Total 1630 1630 0 0

Benchmark Summary (Judy vs PHP Array)

Ratio = Judy / Array. Bold = Judy wins (≤0.95x). Plain = Array is faster/smaller.

Time (Write / Read) — Linux

Scenario PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K 3.5x / 2.7x 3.5x / 2.7x 3.5x / 2.6x 3.4x / 2.7x 3.3x / 2.7x
Sparse Int 500K 4.0x / 2.8x 3.9x / 2.6x 3.9x / 2.8x 3.9x / 3.0x 3.7x / 2.7x
Sparse Int 1M 3.8x / 2.2x 3.7x / 1.9x 4.0x / 2.0x 3.1x / 2.7x 3.4x / 2.6x
Sparse Int 10M 2.3x / 2.5x 2.2x / 2.3x 2.1x / 2.3x 2.1x / 2.5x 2.2x / 2.7x
String 100K 2.2x / 3.0x 2.7x / 3.2x 3.4x / 3.7x 2.6x / 3.5x 2.5x / 3.1x
String 500K 2.3x / 2.2x 2.1x / 1.9x 2.2x / 2.2x 2.3x / 2.3x 2.3x / 2.1x
String 1M 2.4x / 2.3x 2.4x / 1.8x 2.3x / 1.7x 2.5x / 2.0x 2.6x / 1.9x
String 10M 2.7x / 2.3x 2.7x / 2.2x 2.7x / 2.3x 2.6x / 2.3x 2.8x / 2.5x

Memory — Linux

Scenario PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K 0.26x 0.26x 0.26x 0.26x 0.26x
Sparse Int 500K 0.46x 0.46x 0.46x 0.46x 0.46x
Sparse Int 1M 0.46x 0.46x 0.46x 0.46x 0.46x
Sparse Int 10M 0.29x 0.29x 0.29x 0.29x 0.29x
String 100K 0.61x 0.61x 0.61x 0.61x 0.61x
String 500K 0.76x 0.76x 0.76x 0.76x 0.76x
String 1M 0.76x 0.76x 0.76x 0.76x 0.76x
String 10M 0.48x 0.48x 0.48x 0.48x 0.48x

Time (Write / Read) — Windows

Scenario PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K 5.6x / 5.7x 5.7x / 1.9x 6.1x / 3.0x 4.2x / 3.2x 6.1x / 3.3x
Sparse Int 500K 4.0x / 2.6x 5.5x / 3.3x 6.3x / 3.1x 5.5x / 3.0x 5.6x / 3.3x
Sparse Int 1M 3.7x / 1.9x 5.7x / 3.8x 4.5x / 2.0x 4.4x / 2.7x 4.9x / 3.3x
Sparse Int 10M 2.5x / 2.2x 2.9x / 2.3x 2.6x / 2.2x 2.7x / 2.3x 3.5x / 2.5x
String 100K 3.6x / 3.7x 3.3x / 4.4x 4.2x / 4.2x 3.9x / 4.9x 3.8x / 4.7x
String 500K 3.8x / 2.8x 4.0x / 3.0x 3.6x / 3.0x 3.6x / 2.6x 4.0x / 3.1x
String 1M 3.3x / 2.2x 3.6x / 2.2x 3.6x / 2.3x 3.3x / 2.4x 3.6x / 2.2x
String 10M 3.6x / 2.4x 3.4x / 2.4x 3.4x / 2.4x 3.3x / 2.3x 3.3x / 2.1x

Memory — Windows

Scenario PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K 0.23x 0.23x 0.23x 0.23x 0.23x
Sparse Int 500K 0.46x 0.46x 0.46x 0.46x 0.46x
Sparse Int 1M 0.46x 0.46x 0.46x 0.46x 0.46x
Sparse Int 10M 0.29x 0.29x 0.29x 0.29x 0.29x
String 100K 0.51x 0.51x 0.51x 0.51x 0.51x
String 500K 0.76x 0.76x 0.76x 0.76x 0.76x
String 1M 0.76x 0.76x 0.76x 0.76x 0.76x
String 10M 0.48x 0.48x 0.48x 0.48x 0.48x
Raw benchmark data

Write Time — Linux

Scenario Subject PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K Judy 0.0140s 0.0140s 0.0141s 0.0140s 0.0140s
Sparse Int 100K PHP Array 0.0040s 0.0040s 0.0040s 0.0041s 0.0042s
Sparse Int 500K Judy 0.0771s 0.0750s 0.0772s 0.0777s 0.0786s
Sparse Int 500K PHP Array 0.0195s 0.0192s 0.0196s 0.0201s 0.0214s
Sparse Int 1M Judy 0.1890s 0.1855s 0.1895s 0.1600s 0.1839s
Sparse Int 1M PHP Array 0.0491s 0.0497s 0.0468s 0.0522s 0.0548s
Sparse Int 10M Judy 3.2325s 2.9785s 3.0834s 3.4104s 3.2786s
Sparse Int 10M PHP Array 1.4175s 1.3779s 1.4553s 1.5875s 1.4750s
String 100K Judy 0.0217s 0.0216s 0.0215s 0.0226s 0.0261s
String 100K PHP Array 0.0099s 0.0080s 0.0064s 0.0086s 0.0104s
String 500K Judy 0.1629s 0.1454s 0.1597s 0.1677s 0.1648s
String 500K PHP Array 0.0706s 0.0699s 0.0735s 0.0737s 0.0730s
String 1M Judy 0.3675s 0.3742s 0.3678s 0.3866s 0.3958s
String 1M PHP Array 0.1519s 0.1543s 0.1574s 0.1561s 0.1527s
String 10M Judy 5.4291s 5.1699s 5.4286s 5.5450s 5.4456s
String 10M PHP Array 2.0154s 1.9196s 2.0009s 2.1598s 1.9287s

Read Time — Linux

Scenario Subject PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K Judy 0.0096s 0.0096s 0.0095s 0.0096s 0.0096s
Sparse Int 100K PHP Array 0.0035s 0.0035s 0.0036s 0.0036s 0.0035s
Sparse Int 500K Judy 0.0611s 0.0570s 0.0568s 0.0604s 0.0763s
Sparse Int 500K PHP Array 0.0220s 0.0216s 0.0203s 0.0203s 0.0278s
Sparse Int 1M Judy 0.1553s 0.1357s 0.1486s 0.1725s 0.1972s
Sparse Int 1M PHP Array 0.0719s 0.0701s 0.0737s 0.0643s 0.0752s
Sparse Int 10M Judy 3.3722s 3.0813s 3.2257s 3.5196s 3.5204s
Sparse Int 10M PHP Array 1.3608s 1.3260s 1.3965s 1.4316s 1.3051s
String 100K Judy 0.0163s 0.0161s 0.0183s 0.0173s 0.0175s
String 100K PHP Array 0.0054s 0.0051s 0.0049s 0.0050s 0.0056s
String 500K Judy 0.1646s 0.1474s 0.1696s 0.1839s 0.1894s
String 500K PHP Array 0.0751s 0.0777s 0.0774s 0.0813s 0.0891s
String 1M Judy 0.3833s 0.3617s 0.3609s 0.4331s 0.3816s
String 1M PHP Array 0.1698s 0.2058s 0.2127s 0.2207s 0.2024s
String 10M Judy 5.7851s 5.3961s 5.7220s 6.0838s 5.8930s
String 10M PHP Array 2.4857s 2.4303s 2.5065s 2.6398s 2.3955s

Memory — Linux

Scenario Subject PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K Judy 1.84 mb 1.84 mb 1.85 mb 1.85 mb 1.84 mb
Sparse Int 100K PHP Array 7 mb 7 mb 7 mb 7 mb 7 mb
Sparse Int 500K Judy 9.19 mb 9.17 mb 9.19 mb 9.2 mb 9.2 mb
Sparse Int 500K PHP Array 20 mb 20 mb 20 mb 20 mb 20 mb
Sparse Int 1M Judy 18.35 mb 18.36 mb 18.35 mb 18.35 mb 18.38 mb
Sparse Int 1M PHP Array 40 mb 40 mb 40 mb 40 mb 40 mb
Sparse Int 10M Judy 183.69 mb 183.56 mb 183.59 mb 183.49 mb 183.59 mb
Sparse Int 10M PHP Array 640 mb 640 mb 640 mb 640 mb 640 mb
String 100K Judy 3.05 mb 3.05 mb 3.05 mb 3.05 mb 3.05 mb
String 100K PHP Array 5 mb 5 mb 5 mb 5 mb 5 mb
String 500K Judy 15.26 mb 15.26 mb 15.26 mb 15.26 mb 15.26 mb
String 500K PHP Array 20 mb 20 mb 20 mb 20 mb 20 mb
String 1M Judy 30.52 mb 30.52 mb 30.52 mb 30.52 mb 30.52 mb
String 1M PHP Array 40 mb 40 mb 40 mb 40 mb 40 mb
String 10M Judy 305.18 mb 305.18 mb 305.18 mb 305.18 mb 305.18 mb
String 10M PHP Array 640 mb 640 mb 640 mb 640 mb 640 mb

Write Time — Windows

Scenario Subject PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K Judy 0.0320s 0.0389s 0.0321s 0.0325s 0.0330s
Sparse Int 100K PHP Array 0.0057s 0.0068s 0.0053s 0.0077s 0.0054s
Sparse Int 500K Judy 0.1684s 0.1841s 0.1963s 0.1695s 0.1748s
Sparse Int 500K PHP Array 0.0421s 0.0337s 0.0310s 0.0306s 0.0313s
Sparse Int 1M Judy 0.3679s 0.3866s 0.3591s 0.3393s 0.3840s
Sparse Int 1M PHP Array 0.0996s 0.0682s 0.0805s 0.0773s 0.0786s
Sparse Int 10M Judy 5.7812s 5.8191s 5.5856s 5.5630s 6.0417s
Sparse Int 10M PHP Array 2.3169s 1.9893s 2.1654s 2.0723s 1.7386s
String 100K Judy 0.0508s 0.0483s 0.0475s 0.0494s 0.0525s
String 100K PHP Array 0.0143s 0.0147s 0.0113s 0.0126s 0.0140s
String 500K Judy 0.3166s 0.3619s 0.3172s 0.3019s 0.3453s
String 500K PHP Array 0.0840s 0.0905s 0.0882s 0.0847s 0.0870s
String 1M Judy 0.6949s 0.6689s 0.7183s 0.6448s 0.7385s
String 1M PHP Array 0.2137s 0.1883s 0.2005s 0.1971s 0.2062s
String 10M Judy 9.6079s 8.9553s 9.2877s 9.3762s 8.8723s
String 10M PHP Array 2.6429s 2.6507s 2.7009s 2.8345s 2.7259s

Read Time — Windows

Scenario Subject PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K Judy 0.0314s 0.0166s 0.0167s 0.0164s 0.0159s
Sparse Int 100K PHP Array 0.0055s 0.0088s 0.0055s 0.0051s 0.0048s
Sparse Int 500K Judy 0.1226s 0.1216s 0.1071s 0.0944s 0.1229s
Sparse Int 500K PHP Array 0.0476s 0.0369s 0.0341s 0.0318s 0.0369s
Sparse Int 1M Judy 0.2764s 0.2908s 0.2322s 0.2054s 0.3086s
Sparse Int 1M PHP Array 0.1429s 0.0757s 0.1149s 0.0756s 0.0945s
Sparse Int 10M Judy 4.2431s 4.1090s 4.1798s 4.1206s 4.5710s
Sparse Int 10M PHP Array 1.9145s 1.8231s 1.8708s 1.8168s 1.8191s
String 100K Judy 0.0354s 0.0327s 0.0287s 0.0331s 0.0348s
String 100K PHP Array 0.0096s 0.0075s 0.0068s 0.0068s 0.0074s
String 500K Judy 0.2593s 0.2983s 0.2762s 0.2109s 0.2861s
String 500K PHP Array 0.0931s 0.1006s 0.0923s 0.0817s 0.0929s
String 1M Judy 0.5854s 0.5163s 0.5372s 0.4941s 0.5873s
String 1M PHP Array 0.2670s 0.2357s 0.2295s 0.2069s 0.2706s
String 10M Judy 7.9602s 7.4056s 7.7423s 7.7622s 7.2468s
String 10M PHP Array 3.2682s 3.1154s 3.2110s 3.4218s 3.3734s

Memory — Windows

Scenario Subject PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K Judy 1.84 mb 1.85 mb 1.85 mb 1.84 mb 1.84 mb
Sparse Int 100K PHP Array 8 mb 8 mb 8 mb 8 mb 8 mb
Sparse Int 500K Judy 9.19 mb 9.18 mb 9.18 mb 9.17 mb 9.18 mb
Sparse Int 500K PHP Array 20 mb 20 mb 20 mb 20 mb 20 mb
Sparse Int 1M Judy 18.35 mb 18.37 mb 18.36 mb 18.36 mb 18.37 mb
Sparse Int 1M PHP Array 40 mb 40 mb 40 mb 40 mb 40 mb
Sparse Int 10M Judy 183.65 mb 183.61 mb 183.5 mb 183.59 mb 183.68 mb
Sparse Int 10M PHP Array 640 mb 640 mb 640 mb 640 mb 640 mb
String 100K Judy 3.05 mb 3.05 mb 3.05 mb 3.05 mb 3.05 mb
String 100K PHP Array 6 mb 6 mb 6 mb 6 mb 6 mb
String 500K Judy 15.26 mb 15.26 mb 15.26 mb 15.26 mb 15.26 mb
String 500K PHP Array 20 mb 20 mb 20 mb 20 mb 20 mb
String 1M Judy 30.52 mb 30.52 mb 30.52 mb 30.52 mb 30.52 mb
String 1M PHP Array 40 mb 40 mb 40 mb 40 mb 40 mb
String 10M Judy 305.18 mb 305.18 mb 305.18 mb 305.18 mb 305.18 mb
String 10M PHP Array 640 mb 640 mb 640 mb 640 mb 640 mb

Batch & Set Operations Benchmarks

Benchmarks for putAll(), fromArray(), getAll(), toArray(), increment(), and BITSET set operations (union, intersect, diff, xor).

Batch Operations — PHP 8.5 (Linux)
=============================================================
  Judy Batch Operations & Increment Benchmark
=============================================================
  PHP 8.5.3 | Judy ext 2.3.0
  Iterations: 5 (median of each)
=============================================================

===========================================================
  INT_TO_INT — 10,000 elements
===========================================================

  [1. Bulk Add: populate 10000 elements]
  PHP array (foreach assign)                            0.222 ms
  Judy individual $j[$k] = $v                           0.606 ms
  Judy putAll()                                         0.489 ms
  Judy::fromArray()                                     0.492 ms
  putAll() vs individual Judy                        1.24x
  fromArray() vs individual Judy                     1.23x
  Judy putAll() vs PHP array                         2.20x
  Judy fromArray() vs PHP array                      2.21x

  [2. Bulk Get: fetch 1100 keys (incl. 100 missing)]
  PHP array ($a[$k] ?? null)                            0.029 ms
  Judy individual $j[$k]                                0.059 ms
  Judy getAll()                                         0.044 ms
  getAll() vs individual Judy                        1.34x
  Judy getAll() vs PHP array                         1.53x

  [3. Conversion: Judy to PHP array]
  Judy toArray()                                        0.429 ms
  Judy manual foreach loop                              1.296 ms
  toArray() vs manual foreach                        3.02x

  [4. Increment: 10000 ops on 1000 unique keys]
  PHP array $a[$k]++                                    0.207 ms
  Judy $j[$k] = $j[$k] + 1                              0.812 ms
  Judy increment()                                      0.580 ms
  increment() vs manual Judy                         1.40x
  Judy increment() vs PHP array                      2.80x

===========================================================
  STRING_TO_INT — 10,000 elements
===========================================================

  [1. Bulk Add: populate 10000 elements]
  PHP array (foreach assign)                            0.285 ms
  Judy individual $j[$k] = $v                           1.911 ms
  Judy putAll()                                         1.781 ms
  Judy::fromArray()                                     1.805 ms
  putAll() vs individual Judy                        1.07x
  fromArray() vs individual Judy                     1.06x
  Judy putAll() vs PHP array                         6.25x
  Judy fromArray() vs PHP array                      6.33x

  [2. Bulk Get: fetch 1100 keys (incl. 100 missing)]
  PHP array ($a[$k] ?? null)                            0.038 ms
  Judy individual $j[$k]                                0.100 ms
  Judy getAll()                                         0.096 ms
  getAll() vs individual Judy                        1.04x
  Judy getAll() vs PHP array                         2.52x

  [3. Conversion: Judy to PHP array]
  Judy toArray()                                        1.092 ms
  Judy manual foreach loop                              2.217 ms
  toArray() vs manual foreach                        2.03x

  [4. Increment: 10000 ops on 1000 unique keys]
  PHP array $a[$k]++                                    0.308 ms
  Judy $j[$k] = $j[$k] + 1                              1.787 ms
  Judy increment()                                      1.294 ms
  increment() vs manual Judy                         1.38x
  Judy increment() vs PHP array                      4.20x

===========================================================
  INT_TO_INT — 100,000 elements
===========================================================

  [1. Bulk Add: populate 100000 elements]
  PHP array (foreach assign)                            2.762 ms
  Judy individual $j[$k] = $v                           6.730 ms
  Judy putAll()                                         5.436 ms
  Judy::fromArray()                                     5.512 ms
  putAll() vs individual Judy                        1.24x
  fromArray() vs individual Judy                     1.22x
  Judy putAll() vs PHP array                         1.97x
  Judy fromArray() vs PHP array                      2.00x

  [2. Bulk Get: fetch 10100 keys (incl. 100 missing)]
  PHP array ($a[$k] ?? null)                            0.265 ms
  Judy individual $j[$k]                                0.444 ms
  Judy getAll()                                         0.249 ms
  getAll() vs individual Judy                        1.79x
  Judy getAll() vs PHP array                         0.94x

  [3. Conversion: Judy to PHP array]
  Judy toArray()                                        5.093 ms
  Judy manual foreach loop                             13.837 ms
  toArray() vs manual foreach                        2.72x

  [4. Increment: 100000 ops on 1000 unique keys]
  PHP array $a[$k]++                                    2.034 ms
  Judy $j[$k] = $j[$k] + 1                              8.007 ms
  Judy increment()                                      5.689 ms
  increment() vs manual Judy                         1.41x
  Judy increment() vs PHP array                      2.80x

===========================================================
  STRING_TO_INT — 100,000 elements
===========================================================

  [1. Bulk Add: populate 100000 elements]
  PHP array (foreach assign)                            3.402 ms
  Judy individual $j[$k] = $v                          20.559 ms
  Judy putAll()                                        19.236 ms
  Judy::fromArray()                                    19.216 ms
  putAll() vs individual Judy                        1.07x
  fromArray() vs individual Judy                     1.07x
  Judy putAll() vs PHP array                         5.65x
  Judy fromArray() vs PHP array                      5.65x

  [2. Bulk Get: fetch 10100 keys (incl. 100 missing)]
  PHP array ($a[$k] ?? null)                            0.346 ms
  Judy individual $j[$k]                                1.008 ms
  Judy getAll()                                         0.944 ms
  getAll() vs individual Judy                        1.07x
  Judy getAll() vs PHP array                         2.73x

  [3. Conversion: Judy to PHP array]
  Judy toArray()                                       11.878 ms
  Judy manual foreach loop                             23.507 ms
  toArray() vs manual foreach                        1.98x

  [4. Increment: 100000 ops on 1000 unique keys]
  PHP array $a[$k]++                                    2.973 ms
  Judy $j[$k] = $j[$k] + 1                             17.246 ms
  Judy increment()                                     12.314 ms
  increment() vs manual Judy                         1.40x
  Judy increment() vs PHP array                      4.14x

===========================================================
  INT_TO_INT — 500,000 elements
===========================================================

  [1. Bulk Add: populate 500000 elements]
  PHP array (foreach assign)                           12.614 ms
  Judy individual $j[$k] = $v                          34.263 ms
  Judy putAll()                                        27.708 ms
  Judy::fromArray()                                    27.820 ms
  putAll() vs individual Judy                        1.24x
  fromArray() vs individual Judy                     1.23x
  Judy putAll() vs PHP array                         2.20x
  Judy fromArray() vs PHP array                      2.21x

  [2. Bulk Get: fetch 50100 keys (incl. 100 missing)]
  PHP array ($a[$k] ?? null)                            1.600 ms
  Judy individual $j[$k]                                2.456 ms
  Judy getAll()                                         1.511 ms
  getAll() vs individual Judy                        1.63x
  Judy getAll() vs PHP array                         0.94x

  [3. Conversion: Judy to PHP array]
  Judy toArray()                                       25.004 ms
  Judy manual foreach loop                             69.021 ms
  toArray() vs manual foreach                        2.76x

  [4. Increment: 500000 ops on 1000 unique keys]
  PHP array $a[$k]++                                   10.601 ms
  Judy $j[$k] = $j[$k] + 1                             39.858 ms
  Judy increment()                                     28.449 ms
  increment() vs manual Judy                         1.40x
  Judy increment() vs PHP array                      2.68x

===========================================================
  STRING_TO_INT — 500,000 elements
===========================================================

  [1. Bulk Add: populate 500000 elements]
  PHP array (foreach assign)                           16.468 ms
  Judy individual $j[$k] = $v                         104.020 ms
  Judy putAll()                                        98.051 ms
  Judy::fromArray()                                    97.974 ms
  putAll() vs individual Judy                        1.06x
  fromArray() vs individual Judy                     1.06x
  Judy putAll() vs PHP array                         5.95x
  Judy fromArray() vs PHP array                      5.95x

  [2. Bulk Get: fetch 50100 keys (incl. 100 missing)]
  PHP array ($a[$k] ?? null)                            1.866 ms
  Judy individual $j[$k]                                5.545 ms
  Judy getAll()                                         5.125 ms
  getAll() vs individual Judy                        1.08x
  Judy getAll() vs PHP array                         2.75x

  [3. Conversion: Judy to PHP array]
  Judy toArray()                                       60.559 ms
  Judy manual foreach loop                            118.008 ms
  toArray() vs manual foreach                        1.95x

  [4. Increment: 500000 ops on 1000 unique keys]
  PHP array $a[$k]++                                   14.849 ms
  Judy $j[$k] = $j[$k] + 1                             86.403 ms
  Judy increment()                                     61.523 ms
  increment() vs manual Judy                         1.40x
  Judy increment() vs PHP array                      4.14x

=============================================================
  Benchmark complete.
=============================================================
Set Operations — PHP 8.5 (Linux)
=============================================================
  Judy BITSET Set Operations Benchmark
=============================================================
  PHP 8.5.3 | Judy ext 2.3.0
  Overlap: 50% between sets A and B
=============================================================

--- Size: 1,000 indices per set (500 unique + 500 shared) ---

  [Union]
  Judy::union()                                    0.090 ms (median of 5)
  array_replace() keys                             0.012 ms (median of 5)
  Speedup: 0.1x

  [Intersect]
  Judy::intersect()                                0.073 ms (median of 5)
  array_intersect_key()                            0.009 ms (median of 5)
  Speedup: 0.1x

  [Diff]
  Judy::diff()                                     0.070 ms (median of 5)
  array_diff_key()                                 0.013 ms (median of 5)
  Speedup: 0.2x

  [XOR (symmetric difference)]
  Judy::xor()                                      0.084 ms (median of 5)
  array_diff_key() x2 + array_replace()            0.031 ms (median of 5)
  Speedup: 0.4x

  [Memory: union result]
  Judy memoryUsage()                            8.08 kb
  PHP array memory delta                        36.05 kb
  Ratio: PHP uses 4.5x more memory

--- Size: 10,000 indices per set (5000 unique + 5000 shared) ---

  [Union]
  Judy::union()                                    0.593 ms (median of 5)
  array_replace() keys                             0.068 ms (median of 5)
  Speedup: 0.1x

  [Intersect]
  Judy::intersect()                                0.276 ms (median of 5)
  array_intersect_key()                            0.092 ms (median of 5)
  Speedup: 0.3x

  [Diff]
  Judy::diff()                                     0.275 ms (median of 5)
  array_diff_key()                                 0.056 ms (median of 5)
  Speedup: 0.2x

  [XOR (symmetric difference)]
  Judy::xor()                                      0.538 ms (median of 5)
  array_diff_key() x2 + array_replace()            0.172 ms (median of 5)
  Speedup: 0.3x

  [Memory: union result]
  Judy memoryUsage()                            8.08 kb
  PHP array memory delta                        260.05 kb
  Ratio: PHP uses 32.2x more memory

--- Size: 100,000 indices per set (50000 unique + 50000 shared) ---

  [Union]
  Judy::union()                                    7.341 ms (median of 5)
  array_replace() keys                             1.116 ms (median of 5)
  Speedup: 0.2x

  [Intersect]
  Judy::intersect()                                3.340 ms (median of 5)
  array_intersect_key()                            1.010 ms (median of 5)
  Speedup: 0.3x

  [Diff]
  Judy::diff()                                     3.177 ms (median of 5)
  array_diff_key()                                 0.567 ms (median of 5)
  Speedup: 0.2x

  [XOR (symmetric difference)]
  Judy::xor()                                      6.663 ms (median of 5)
  array_diff_key() x2 + array_replace()            2.170 ms (median of 5)
  Speedup: 0.3x

  [Memory: union result]
  Judy memoryUsage()                            20.08 kb
  PHP array memory delta                        4 mb
  Ratio: PHP uses 204.2x more memory

--- Size: 500,000 indices per set (250000 unique + 250000 shared) ---

  [Union]
  Judy::union()                                   37.421 ms (median of 5)
  array_replace() keys                             4.987 ms (median of 5)
  Speedup: 0.1x

  [Intersect]
  Judy::intersect()                               16.927 ms (median of 5)
  array_intersect_key()                            5.866 ms (median of 5)
  Speedup: 0.3x

  [Diff]
  Judy::diff()                                    16.702 ms (median of 5)
  array_diff_key()                                 3.254 ms (median of 5)
  Speedup: 0.2x

  [XOR (symmetric difference)]
  Judy::xor()                                     33.870 ms (median of 5)
  array_diff_key() x2 + array_replace()           11.724 ms (median of 5)
  Speedup: 0.3x

  [Memory: union result]
  Judy memoryUsage()                            56.08 kb
  PHP array memory delta                        16 mb
  Ratio: PHP uses 292.2x more memory

=============================================================
  Benchmark complete.
=============================================================

All-Types Comparison Benchmark

Side-by-side comparison of all six Judy types and native PHP array (50K elements, 3 iterations, Linux only).

All-Types Benchmark — PHP 8.5 (Linux)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  Judy All-Types Benchmark — 500,000 elements, 3 iterations (median)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  PHP 8.5.3 | Judy ext 2.3.0
  INT_TO_PACKED: supported
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

┌─ Integer-keyed types (500,000 elements) ──────────────────────────────┐

  ── bool values (BITSET use-case) ──

                                 Write        Read     Foreach        Free      Heap delta
  ────────────────────────  ──────────  ──────────  ──────────  ──────────  ──────────────
  PHP array (bool)             7.08 ms     3.86 ms     3.13 ms     0.34 ms          8.0 MB
  BITSET                      19.62 ms    11.21 ms    53.16 ms     0.01 ms           160 B

  ── integer values ──

                                 Write        Read     Foreach        Free      Heap delta
  ────────────────────────  ──────────  ──────────  ──────────  ──────────  ──────────────
  PHP array (int)              7.36 ms     3.89 ms     3.12 ms     0.37 ms          8.0 MB
  INT_TO_INT                  29.26 ms    13.27 ms    54.85 ms     0.24 ms           160 B

  ── mixed values (str/int/array/bool) ──

                                 Write        Read     Foreach        Free      Heap delta
  ────────────────────────  ──────────  ──────────  ──────────  ──────────  ──────────────
  PHP array (mixed)           20.02 ms     5.78 ms     3.93 ms     3.92 ms          8.0 MB
  INT_TO_MIXED                39.06 ms    16.45 ms    55.87 ms    14.62 ms          7.6 MB
  INT_TO_PACKED               70.51 ms    47.16 ms    84.31 ms     9.38 ms         13.1 MB

┌─ String-keyed types (500,000 elements) ───────────────────────────────┐

  ── integer values ──

                                 Write        Read     Foreach        Free      Heap delta
  ────────────────────────  ──────────  ──────────  ──────────  ──────────  ──────────────
  PHP array (str→int)       17.07 ms     7.84 ms     4.72 ms     1.29 ms         20.0 MB
  STRING_TO_INT              103.05 ms    41.34 ms    98.65 ms    21.94 ms           160 B
  STRING_TO_INT_HASH         183.03 ms    27.06 ms   117.65 ms    47.18 ms           160 B

  ── mixed values (str/int/array/bool) ──

                                 Write        Read     Foreach        Free      Heap delta
  ────────────────────────  ──────────  ──────────  ──────────  ──────────  ──────────────
  PHP array (str→mixed)     19.57 ms     9.69 ms     6.16 ms     6.59 ms         20.0 MB
  STRING_TO_MIXED            125.50 ms    45.11 ms   107.67 ms    80.04 ms          7.6 MB
  STRING_TO_MIXED_HASH       259.83 ms    42.54 ms   139.26 ms   148.31 ms          7.6 MB

┌─ Long-key string types (100,000 elements, 128-byte keys) ──────────────┐

  ── 128-byte keys → int ──

                                 Write        Read     Foreach        Free      Heap delta
  ────────────────────────  ──────────  ──────────  ──────────  ──────────  ──────────────
  PHP array (long→int)       4.61 ms     2.08 ms     1.01 ms     0.51 ms          5.0 MB
  STRING_TO_INT (long)        22.11 ms     7.15 ms    22.68 ms     6.63 ms           160 B
  STR_TO_MIX_HASH (long)     119.04 ms    26.92 ms    54.76 ms    76.06 ms          1.5 MB
  STR_TO_INT_HASH (long)      88.52 ms    24.53 ms    49.75 ms    39.33 ms           160 B

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  SUMMARY — All types, 500,000 elements (median of 3 iterations)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  Type                    Keys      Values    Write(ms)    Read(ms)    Iter(ms)    Free(ms)      Heap delta
  ───────────────────────────────────────────────────────────────────────────────────────────────────────────
  PHP array (bool)        int       bool           7.08        3.86        3.13        0.34          8.0 MB
  BITSET                  int       bool          19.62       11.21       53.16        0.01           160 B
  PHP array (int)         int       int            7.36        3.89        3.12        0.37          8.0 MB
  INT_TO_INT              int       int           29.26       13.27       54.85        0.24           160 B
  PHP array (mixed)       int       mixed         20.02        5.78        3.93        3.92          8.0 MB
  INT_TO_MIXED            int       mixed         39.06       16.45       55.87       14.62          7.6 MB
  INT_TO_PACKED           int       mixed         70.51       47.16       84.31        9.38         13.1 MB
  ···········································································································
  PHP array (str→int)   string    int           17.07        7.84        4.72        1.29         20.0 MB
  STRING_TO_INT           string    int          103.05       41.34       98.65       21.94           160 B
  STRING_TO_INT_HASH      string    int          183.03       27.06      117.65       47.18           160 B
  PHP array (str→mixed)  string    mixed         19.57        9.69        6.16        6.59         20.0 MB
  STRING_TO_MIXED         string    mixed        125.50       45.11      107.67       80.04          7.6 MB
  STRING_TO_MIXED_HASH    string    mixed        259.83       42.54      139.26      148.31          7.6 MB
  ···········································································································
  PHP array (long→int)  str128    int            4.61        2.08        1.01        0.51          5.0 MB
  STRING_TO_INT (long)    str128    int           22.11        7.15       22.68        6.63           160 B
  STR_TO_MIX_HASH (long)  str128    int          119.04       26.92       54.76       76.06          1.5 MB
  STR_TO_INT_HASH (long)  str128    int           88.52       24.53       49.75       39.33           160 B

  Notes:
  • Write/Read/Iter: median of 3 iterations via hrtime(true)
  • Free: median time to unset() + gc_collect_cycles() a populated container
  • Heap delta: memory_get_usage() before/after populate (emalloc'd PHP heap)
  • Long keys: 128-byte keys, capped at 100,000 elements
    – Demonstrates JudyHS O(1) hash vs JudySL O(k) trie traversal
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  Benchmark complete — 2026-03-01 19:04:58
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

@orieg orieg merged commit f59958a into main Mar 1, 2026
13 checks passed
@orieg orieg deleted the optimize/iterator-overhaul branch March 1, 2026 19:07
@orieg orieg mentioned this pull request Mar 1, 2026
1 task
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.

1 participant