Skip to content

perf: tagged-union packed format eliminates serialize for scalar values#55

Merged
orieg merged 2 commits intomainfrom
optimize/packed-fastpath
Mar 1, 2026
Merged

perf: tagged-union packed format eliminates serialize for scalar values#55
orieg merged 2 commits intomainfrom
optimize/packed-fastpath

Conversation

@orieg
Copy link
Owner

@orieg orieg commented Mar 1, 2026

Summary

  • Replaces php_var_serialize/php_var_unserialize in judy_pack_value/judy_unpack_value with a direct tagged-union struct (judy_packed_value) for the five common scalar types: long, double, true, false, null
  • Uses a uint8_t tag + union to store values inline; only non-scalar zvals fall back to the serialization path (tag 255)
  • Updates slice to use judy_packed_value_size() + memcpy instead of unpacking and repacking each value

Test plan

  • All existing tests pass
  • tests/packed_scalar_fastpath_001.phpt — long/double/bool/null round-trip through the fastpath
  • tests/packed_complex_fallback_001.phpt — arrays/objects fall back to serialize correctly

@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 enhances the performance of Judy arrays by introducing a specialized packed format for scalar values. Instead of serializing and unserializing every value, common scalar types are now stored directly within a tagged union, leading to faster read and write operations. Complex types gracefully fall back to the existing serialization mechanism, ensuring compatibility while providing a substantial speed boost for frequently used data types.

Highlights

  • Performance Optimization for Scalar Values: Implemented a direct tagged-union (judy_packed_value) for storing common scalar types (long, double, true, false, null, string) in Judy arrays, eliminating the need for php_var_serialize/unserialize for these types.
  • Serialization Fallback for Complex Types: Ensured that non-scalar zvals (arrays, objects) continue to use the serialization path, identified by a specific tag (255), maintaining compatibility.
  • Optimized Value Copying: Optimized the slice method to use judy_packed_value_size() and memcpy for efficient copying of packed values, avoiding unnecessary unpacking and repacking.
Changelog
  • judy_handlers.c
    • Updated judy_object_clone to use the new judy_packed_value_size function for accurate memory allocation when cloning packed values.
  • php_judy.c
    • Modified judy_pack_value to directly pack scalar types (long, double, true, false, null, string) using a tag and union, falling back to php_var_serialize for other types.
    • Modified judy_unpack_value to reconstruct scalar types directly based on their tag, using php_var_unserialize only for previously serialized values.
    • Updated PHP_METHOD(judy, slice) to leverage judy_packed_value_size for efficient copying of packed values.
  • php_judy.h
    • Redefined the _judy_packed_value struct as a tagged union, allowing direct storage of scalar values and a tag field to differentiate types.
    • Added an inline function judy_packed_value_size to dynamically calculate the size of a judy_packed_value based on its stored type.
  • tests/packed_complex_fallback_001.phpt
    • Added a new test file to verify the correct serialization and deserialization of complex types (arrays and objects) when using the INT_TO_PACKED mode.
  • tests/packed_scalar_fastpath_001.phpt
    • Added a new test file to confirm the accurate packing and unpacking of various scalar types (integers, floats, booleans, null, strings) through the new fast-path mechanism.
Activity
  • The author has provided a detailed summary of the changes and the motivation behind them.
  • A comprehensive test plan has been outlined, including verification of existing tests and the introduction of new tests specifically for the scalar fast-path and complex type fallback.
  • New test files (tests/packed_complex_fallback_001.phpt and tests/packed_scalar_fastpath_001.phpt) have been added to validate the new functionality.
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 a significant performance improvement for INT_TO_PACKED Judy arrays by creating a fast path for scalar values, avoiding costly serialization and unserialization. The new implementation uses a tagged union to store scalars directly. The changes are well-implemented and accompanied by good test coverage for both the new fast path and the existing fallback mechanism. I have a couple of suggestions to improve maintainability and fix a potential memory issue in an edge case.

@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 - 165 165 0 0 1.3s
8.2 Linux x64 - 165 165 0 0 1.8s
8.3 Linux x64 - 165 165 0 0 1.7s
8.4 Linux x64 - 165 165 0 0 1.8s
8.5 Linux x64 - 165 165 0 0 1.8s
8.1 Windows x64 nts 165 165 0 0 7.8s
8.2 Windows x64 nts 165 165 0 0 7.8s
8.3 Windows x64 nts 165 165 0 0 7.9s
8.4 Windows x64 nts 165 165 0 0 9.9s
8.5 Windows x64 nts 165 165 0 0 7.9s
Total 1650 1650 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.6x 3.6x / 2.9x 3.6x / 2.6x 3.5x / 2.8x 3.5x / 2.8x
Sparse Int 500K 4.1x / 2.8x 3.8x / 2.8x 3.7x / 2.8x 3.6x / 2.1x 3.6x / 2.5x
Sparse Int 1M 4.1x / 2.5x 3.7x / 2.7x 4.3x / 2.6x 4.1x / 3.1x 3.8x / 2.4x
Sparse Int 10M 2.3x / 2.5x 2.2x / 2.5x 2.1x / 2.4x 2.2x / 2.5x 2.0x / 2.5x
String 100K 3.2x / 3.1x 3.1x / 3.8x 2.8x / 3.1x 3.5x / 4.5x 3.7x / 3.3x
String 500K 2.4x / 2.6x 2.2x / 2.3x 2.1x / 2.2x 2.5x / 2.6x 2.4x / 2.5x
String 1M 2.5x / 2.4x 2.6x / 2.2x 2.5x / 2.3x 2.6x / 2.3x 2.6x / 2.1x
String 10M 2.7x / 2.3x 2.8x / 2.4x 2.7x / 2.4x 2.8x / 2.5x 2.6x / 2.3x

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 4.3x / 3.1x 5.3x / 2.6x 6.0x / 3.0x 5.7x / 1.6x 4.5x / 2.8x
Sparse Int 500K 5.3x / 2.8x 5.3x / 2.7x 5.0x / 2.6x 4.4x / 1.6x 5.0x / 3.0x
Sparse Int 1M 4.5x / 2.7x 4.6x / 2.7x 4.8x / 3.0x 4.2x / 1.7x 5.2x / 2.9x
Sparse Int 10M 2.6x / 2.2x 2.5x / 2.3x 2.7x / 2.1x 4.1x / 2.2x 3.6x / 2.3x
String 100K 4.4x / 4.2x 3.7x / 4.4x 3.2x / 3.6x 4.2x / 2.8x 5.0x / 5.3x
String 500K 3.9x / 2.6x 3.5x / 2.6x 3.9x / 2.7x 3.9x / 2.3x 3.9x / 2.8x
String 1M 3.7x / 2.2x 3.3x / 2.4x 3.5x / 2.3x 4.2x / 2.4x 3.5x / 2.1x
String 10M 3.6x / 2.4x 3.8x / 2.4x 3.5x / 2.3x 4.1x / 2.5x 3.5x / 2.4x

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.0138s 0.0146s 0.0142s 0.0145s 0.0143s
Sparse Int 100K PHP Array 0.0039s 0.0040s 0.0040s 0.0041s 0.0041s
Sparse Int 500K Judy 0.0766s 0.0758s 0.0742s 0.0784s 0.0772s
Sparse Int 500K PHP Array 0.0189s 0.0200s 0.0200s 0.0219s 0.0216s
Sparse Int 1M Judy 0.1854s 0.1977s 0.1972s 0.2042s 0.1944s
Sparse Int 1M PHP Array 0.0457s 0.0535s 0.0462s 0.0493s 0.0512s
Sparse Int 10M Judy 3.0472s 3.3225s 3.1555s 3.1199s 3.1921s
Sparse Int 10M PHP Array 1.3122s 1.5256s 1.4818s 1.4487s 1.5722s
String 100K Judy 0.0203s 0.0294s 0.0208s 0.0279s 0.0252s
String 100K PHP Array 0.0064s 0.0095s 0.0074s 0.0079s 0.0069s
String 500K Judy 0.1568s 0.1665s 0.1530s 0.1670s 0.1687s
String 500K PHP Array 0.0660s 0.0751s 0.0715s 0.0662s 0.0697s
String 1M Judy 0.3576s 0.3979s 0.3752s 0.3808s 0.3863s
String 1M PHP Array 0.1429s 0.1522s 0.1530s 0.1478s 0.1479s
String 10M Judy 5.2638s 5.6646s 5.3998s 5.4778s 5.2651s
String 10M PHP Array 1.9435s 2.0580s 1.9691s 1.9441s 2.0151s

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.0095s 0.0103s 0.0095s 0.0100s 0.0099s
Sparse Int 100K PHP Array 0.0037s 0.0035s 0.0036s 0.0036s 0.0036s
Sparse Int 500K Judy 0.0569s 0.0626s 0.0613s 0.0589s 0.0675s
Sparse Int 500K PHP Array 0.0202s 0.0226s 0.0220s 0.0287s 0.0274s
Sparse Int 1M Judy 0.1664s 0.1739s 0.1484s 0.2036s 0.1759s
Sparse Int 1M PHP Array 0.0658s 0.0653s 0.0561s 0.0657s 0.0740s
Sparse Int 10M Judy 3.2129s 3.5873s 3.3428s 3.3932s 3.5716s
Sparse Int 10M PHP Array 1.3066s 1.4386s 1.3954s 1.3747s 1.4218s
String 100K Judy 0.0162s 0.0210s 0.0163s 0.0224s 0.0171s
String 100K PHP Array 0.0052s 0.0055s 0.0052s 0.0050s 0.0052s
String 500K Judy 0.1606s 0.1753s 0.1567s 0.1798s 0.1776s
String 500K PHP Array 0.0624s 0.0752s 0.0698s 0.0701s 0.0720s
String 1M Judy 0.3682s 0.4100s 0.3753s 0.3964s 0.4037s
String 1M PHP Array 0.1562s 0.1885s 0.1667s 0.1757s 0.1935s
String 10M Judy 5.5692s 6.1512s 5.8370s 5.8910s 5.6884s
String 10M PHP Array 2.4498s 2.6009s 2.4600s 2.4001s 2.4660s

Memory — Linux

Scenario Subject PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K Judy 1.85 mb 1.83 mb 1.85 mb 1.85 mb 1.85 mb
Sparse Int 100K PHP Array 7 mb 7 mb 7 mb 7 mb 7 mb
Sparse Int 500K Judy 9.17 mb 9.18 mb 9.18 mb 9.18 mb 9.17 mb
Sparse Int 500K PHP Array 20 mb 20 mb 20 mb 20 mb 20 mb
Sparse Int 1M Judy 18.36 mb 18.39 mb 18.37 mb 18.38 mb 18.38 mb
Sparse Int 1M PHP Array 40 mb 40 mb 40 mb 40 mb 40 mb
Sparse Int 10M Judy 183.57 mb 183.57 mb 183.56 mb 183.62 mb 183.56 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.0321s 0.0342s 0.0326s 0.0370s 0.0323s
Sparse Int 100K PHP Array 0.0074s 0.0065s 0.0054s 0.0065s 0.0072s
Sparse Int 500K Judy 0.1643s 0.1683s 0.1685s 0.2316s 0.1660s
Sparse Int 500K PHP Array 0.0309s 0.0315s 0.0335s 0.0531s 0.0329s
Sparse Int 1M Judy 0.3398s 0.3493s 0.3603s 0.5304s 0.3452s
Sparse Int 1M PHP Array 0.0755s 0.0760s 0.0757s 0.1272s 0.0666s
Sparse Int 10M Judy 5.1412s 5.6054s 5.3694s 7.9259s 5.6269s
Sparse Int 10M PHP Array 1.9607s 2.2194s 1.9877s 1.9220s 1.5652s
String 100K Judy 0.0458s 0.0480s 0.0470s 0.0621s 0.0532s
String 100K PHP Array 0.0105s 0.0131s 0.0145s 0.0148s 0.0107s
String 500K Judy 0.2958s 0.3156s 0.3128s 0.4034s 0.3030s
String 500K PHP Array 0.0758s 0.0910s 0.0812s 0.1043s 0.0782s
String 1M Judy 0.6266s 0.6734s 0.6468s 0.8735s 0.6435s
String 1M PHP Array 0.1702s 0.2048s 0.1869s 0.2077s 0.1860s
String 10M Judy 8.8584s 10.4212s 8.9530s 12.5891s 8.8245s
String 10M PHP Array 2.4337s 2.7307s 2.5478s 3.0813s 2.5122s

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.0193s 0.0158s 0.0165s 0.0193s 0.0155s
Sparse Int 100K PHP Array 0.0063s 0.0060s 0.0055s 0.0117s 0.0056s
Sparse Int 500K Judy 0.0896s 0.0985s 0.0937s 0.1570s 0.0931s
Sparse Int 500K PHP Array 0.0315s 0.0366s 0.0359s 0.0968s 0.0312s
Sparse Int 1M Judy 0.2204s 0.2408s 0.2449s 0.3581s 0.2104s
Sparse Int 1M PHP Array 0.0823s 0.0876s 0.0821s 0.2067s 0.0720s
Sparse Int 10M Judy 3.9632s 4.1997s 3.9299s 5.3616s 3.9241s
Sparse Int 10M PHP Array 1.8120s 1.8603s 1.8447s 2.4204s 1.7284s
String 100K Judy 0.0276s 0.0324s 0.0283s 0.0489s 0.0331s
String 100K PHP Array 0.0066s 0.0074s 0.0078s 0.0176s 0.0062s
String 500K Judy 0.2047s 0.2289s 0.2355s 0.2901s 0.2109s
String 500K PHP Array 0.0796s 0.0896s 0.0863s 0.1263s 0.0746s
String 1M Judy 0.4864s 0.5504s 0.5019s 0.6378s 0.5021s
String 1M PHP Array 0.2165s 0.2274s 0.2179s 0.2669s 0.2420s
String 10M Judy 7.1573s 7.6911s 7.0301s 9.3652s 7.1637s
String 10M PHP Array 3.0347s 3.1818s 3.0355s 3.8048s 3.0153s

Memory — Windows

Scenario Subject PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4 PHP 8.5
Sparse Int 100K Judy 1.85 mb 1.84 mb 1.84 mb 1.85 mb 1.85 mb
Sparse Int 100K PHP Array 8 mb 8 mb 8 mb 8 mb 8 mb
Sparse Int 500K Judy 9.19 mb 9.2 mb 9.19 mb 9.19 mb 9.19 mb
Sparse Int 500K PHP Array 20 mb 20 mb 20 mb 20 mb 20 mb
Sparse Int 1M Judy 18.36 mb 18.36 mb 18.36 mb 18.38 mb 18.36 mb
Sparse Int 1M PHP Array 40 mb 40 mb 40 mb 40 mb 40 mb
Sparse Int 10M Judy 183.52 mb 183.57 mb 183.66 mb 183.66 mb 183.6 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.226 ms
  Judy individual $j[$k] = $v                           0.611 ms
  Judy putAll()                                         0.486 ms
  Judy::fromArray()                                     0.485 ms
  putAll() vs individual Judy                        1.26x
  fromArray() vs individual Judy                     1.26x
  Judy putAll() vs PHP array                         2.15x
  Judy fromArray() vs PHP array                      2.14x

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

  [3. Conversion: Judy to PHP array]
  Judy toArray()                                        0.428 ms
  Judy manual foreach loop                              1.262 ms
  toArray() vs manual foreach                        2.95x

  [4. Increment: 10000 ops on 1000 unique keys]
  PHP array $a[$k]++                                    0.217 ms
  Judy $j[$k] = $j[$k] + 1                              0.811 ms
  Judy increment()                                      0.601 ms
  increment() vs manual Judy                         1.35x
  Judy increment() vs PHP array                      2.76x

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

  [1. Bulk Add: populate 10000 elements]
  PHP array (foreach assign)                            0.284 ms
  Judy individual $j[$k] = $v                           1.929 ms
  Judy putAll()                                         1.802 ms
  Judy::fromArray()                                     1.792 ms
  putAll() vs individual Judy                        1.07x
  fromArray() vs individual Judy                     1.08x
  Judy putAll() vs PHP array                         6.34x
  Judy fromArray() vs PHP array                      6.31x

  [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.094 ms
  getAll() vs individual Judy                        1.06x
  Judy getAll() vs PHP array                         2.50x

  [3. Conversion: Judy to PHP array]
  Judy toArray()                                        1.103 ms
  Judy manual foreach loop                              2.289 ms
  toArray() vs manual foreach                        2.07x

  [4. Increment: 10000 ops on 1000 unique keys]
  PHP array $a[$k]++                                    0.310 ms
  Judy $j[$k] = $j[$k] + 1                              1.810 ms
  Judy increment()                                      1.342 ms
  increment() vs manual Judy                         1.35x
  Judy increment() vs PHP array                      4.32x

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

  [1. Bulk Add: populate 100000 elements]
  PHP array (foreach assign)                            2.730 ms
  Judy individual $j[$k] = $v                           6.620 ms
  Judy putAll()                                         5.378 ms
  Judy::fromArray()                                     5.379 ms
  putAll() vs individual Judy                        1.23x
  fromArray() vs individual Judy                     1.23x
  Judy putAll() vs PHP array                         1.97x
  Judy fromArray() vs PHP array                      1.97x

  [2. Bulk Get: fetch 10100 keys (incl. 100 missing)]
  PHP array ($a[$k] ?? null)                            0.265 ms
  Judy individual $j[$k]                                0.435 ms
  Judy getAll()                                         0.252 ms
  getAll() vs individual Judy                        1.73x
  Judy getAll() vs PHP array                         0.95x

  [3. Conversion: Judy to PHP array]
  Judy toArray()                                        5.071 ms
  Judy manual foreach loop                             13.359 ms
  toArray() vs manual foreach                        2.63x

  [4. Increment: 100000 ops on 1000 unique keys]
  PHP array $a[$k]++                                    2.036 ms
  Judy $j[$k] = $j[$k] + 1                              7.918 ms
  Judy increment()                                      5.861 ms
  increment() vs manual Judy                         1.35x
  Judy increment() vs PHP array                      2.88x

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

  [1. Bulk Add: populate 100000 elements]
  PHP array (foreach assign)                            3.427 ms
  Judy individual $j[$k] = $v                          20.458 ms
  Judy putAll()                                        19.394 ms
  Judy::fromArray()                                    19.374 ms
  putAll() vs individual Judy                        1.05x
  fromArray() vs individual Judy                     1.06x
  Judy putAll() vs PHP array                         5.66x
  Judy fromArray() vs PHP array                      5.65x

  [2. Bulk Get: fetch 10100 keys (incl. 100 missing)]
  PHP array ($a[$k] ?? null)                            0.342 ms
  Judy individual $j[$k]                                0.998 ms
  Judy getAll()                                         0.951 ms
  getAll() vs individual Judy                        1.05x
  Judy getAll() vs PHP array                         2.78x

  [3. Conversion: Judy to PHP array]
  Judy toArray()                                       11.817 ms
  Judy manual foreach loop                             23.529 ms
  toArray() vs manual foreach                        1.99x

  [4. Increment: 100000 ops on 1000 unique keys]
  PHP array $a[$k]++                                    2.987 ms
  Judy $j[$k] = $j[$k] + 1                             17.252 ms
  Judy increment()                                     12.431 ms
  increment() vs manual Judy                         1.39x
  Judy increment() vs PHP array                      4.16x

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

  [1. Bulk Add: populate 500000 elements]
  PHP array (foreach assign)                           12.437 ms
  Judy individual $j[$k] = $v                          33.704 ms
  Judy putAll()                                        27.706 ms
  Judy::fromArray()                                    27.579 ms
  putAll() vs individual Judy                        1.22x
  fromArray() vs individual Judy                     1.22x
  Judy putAll() vs PHP array                         2.23x
  Judy fromArray() vs PHP array                      2.22x

  [2. Bulk Get: fetch 50100 keys (incl. 100 missing)]
  PHP array ($a[$k] ?? null)                            1.560 ms
  Judy individual $j[$k]                                2.416 ms
  Judy getAll()                                         1.506 ms
  getAll() vs individual Judy                        1.60x
  Judy getAll() vs PHP array                         0.97x

  [3. Conversion: Judy to PHP array]
  Judy toArray()                                       24.596 ms
  Judy manual foreach loop                             65.675 ms
  toArray() vs manual foreach                        2.67x

  [4. Increment: 500000 ops on 1000 unique keys]
  PHP array $a[$k]++                                   11.240 ms
  Judy $j[$k] = $j[$k] + 1                             41.045 ms
  Judy increment()                                     29.549 ms
  increment() vs manual Judy                         1.39x
  Judy increment() vs PHP array                      2.63x

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

  [1. Bulk Add: populate 500000 elements]
  PHP array (foreach assign)                           16.373 ms
  Judy individual $j[$k] = $v                         104.202 ms
  Judy putAll()                                        98.427 ms
  Judy::fromArray()                                    98.844 ms
  putAll() vs individual Judy                        1.06x
  fromArray() vs individual Judy                     1.05x
  Judy putAll() vs PHP array                         6.01x
  Judy fromArray() vs PHP array                      6.04x

  [2. Bulk Get: fetch 50100 keys (incl. 100 missing)]
  PHP array ($a[$k] ?? null)                            1.881 ms
  Judy individual $j[$k]                                5.305 ms
  Judy getAll()                                         5.112 ms
  getAll() vs individual Judy                        1.04x
  Judy getAll() vs PHP array                         2.72x

  [3. Conversion: Judy to PHP array]
  Judy toArray()                                       60.687 ms
  Judy manual foreach loop                            118.758 ms
  toArray() vs manual foreach                        1.96x

  [4. Increment: 500000 ops on 1000 unique keys]
  PHP array $a[$k]++                                   14.938 ms
  Judy $j[$k] = $j[$k] + 1                             86.452 ms
  Judy increment()                                     61.676 ms
  increment() vs manual Judy                         1.40x
  Judy increment() vs PHP array                      4.13x

=============================================================
  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.089 ms (median of 5)
  array_replace() keys                             0.015 ms (median of 5)
  Speedup: 0.2x

  [Intersect]
  Judy::intersect()                                0.042 ms (median of 5)
  array_intersect_key()                            0.008 ms (median of 5)
  Speedup: 0.2x

  [Diff]
  Judy::diff()                                     0.041 ms (median of 5)
  array_diff_key()                                 0.011 ms (median of 5)
  Speedup: 0.3x

  [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.592 ms (median of 5)
  array_replace() keys                             0.068 ms (median of 5)
  Speedup: 0.1x

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

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

  [XOR (symmetric difference)]
  Judy::xor()                                      0.540 ms (median of 5)
  array_diff_key() x2 + array_replace()            0.163 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.225 ms (median of 5)
  array_replace() keys                             1.052 ms (median of 5)
  Speedup: 0.1x

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

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

  [XOR (symmetric difference)]
  Judy::xor()                                      6.645 ms (median of 5)
  array_diff_key() x2 + array_replace()            2.184 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.591 ms (median of 5)
  array_replace() keys                             4.789 ms (median of 5)
  Speedup: 0.1x

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

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

  [XOR (symmetric difference)]
  Judy::xor()                                     33.999 ms (median of 5)
  array_diff_key() x2 + array_replace()           11.600 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)             6.92 ms     3.88 ms     3.12 ms     0.33 ms          8.0 MB
  BITSET                      19.72 ms    11.27 ms    50.38 ms     0.01 ms           160 B

  ── integer values ──

                                 Write        Read     Foreach        Free      Heap delta
  ────────────────────────  ──────────  ──────────  ──────────  ──────────  ──────────────
  PHP array (int)              7.62 ms     3.88 ms     3.11 ms     0.32 ms          8.0 MB
  INT_TO_INT                  28.89 ms    13.17 ms    47.11 ms     0.31 ms           160 B

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

                                 Write        Read     Foreach        Free      Heap delta
  ────────────────────────  ──────────  ──────────  ──────────  ──────────  ──────────────
  PHP array (mixed)           19.72 ms     5.80 ms     3.79 ms     4.04 ms          8.0 MB
  INT_TO_MIXED                39.12 ms    16.29 ms    50.01 ms    13.91 ms          7.6 MB
  INT_TO_PACKED               51.28 ms    33.59 ms    61.83 ms     9.10 ms         13.3 MB

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

  ── integer values ──

                                 Write        Read     Foreach        Free      Heap delta
  ────────────────────────  ──────────  ──────────  ──────────  ──────────  ──────────────
  PHP array (str→int)       16.32 ms     7.34 ms     4.53 ms     1.09 ms         20.0 MB
  STRING_TO_INT              102.39 ms    41.30 ms    95.12 ms    21.92 ms           160 B
  STRING_TO_INT_HASH         176.82 ms    26.51 ms   111.50 ms    47.04 ms           160 B

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

                                 Write        Read     Foreach        Free      Heap delta
  ────────────────────────  ──────────  ──────────  ──────────  ──────────  ──────────────
  PHP array (str→mixed)     19.13 ms     9.51 ms     6.02 ms     6.22 ms         20.0 MB
  STRING_TO_MIXED            124.91 ms    44.78 ms   106.22 ms    80.06 ms          7.6 MB
  STRING_TO_MIXED_HASH       264.07 ms    41.74 ms   135.27 ms   142.57 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.41 ms     2.05 ms     0.97 ms     0.46 ms          5.0 MB
  STRING_TO_INT (long)        21.29 ms     7.04 ms    21.92 ms     6.66 ms           160 B
  STR_TO_MIX_HASH (long)     105.06 ms    22.49 ms    45.85 ms    64.77 ms          1.5 MB
  STR_TO_INT_HASH (long)      80.63 ms    20.39 ms    43.08 ms    29.59 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           6.92        3.88        3.12        0.33          8.0 MB
  BITSET                  int       bool          19.72       11.27       50.38        0.01           160 B
  PHP array (int)         int       int            7.62        3.88        3.11        0.32          8.0 MB
  INT_TO_INT              int       int           28.89       13.17       47.11        0.31           160 B
  PHP array (mixed)       int       mixed         19.72        5.80        3.79        4.04          8.0 MB
  INT_TO_MIXED            int       mixed         39.12       16.29       50.01       13.91          7.6 MB
  INT_TO_PACKED           int       mixed         51.28       33.59       61.83        9.10         13.3 MB
  ···········································································································
  PHP array (str→int)   string    int           16.32        7.34        4.53        1.09         20.0 MB
  STRING_TO_INT           string    int          102.39       41.30       95.12       21.92           160 B
  STRING_TO_INT_HASH      string    int          176.82       26.51      111.50       47.04           160 B
  PHP array (str→mixed)  string    mixed         19.13        9.51        6.02        6.22         20.0 MB
  STRING_TO_MIXED         string    mixed        124.91       44.78      106.22       80.06          7.6 MB
  STRING_TO_MIXED_HASH    string    mixed        264.07       41.74      135.27      142.57          7.6 MB
  ···········································································································
  PHP array (long→int)  str128    int            4.41        2.05        0.97        0.46          5.0 MB
  STRING_TO_INT (long)    str128    int           21.29        7.04       21.92        6.66           160 B
  STR_TO_MIX_HASH (long)  str128    int          105.06       22.49       45.85       64.77          1.5 MB
  STR_TO_INT_HASH (long)  str128    int           80.63       20.39       43.08       29.59           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:12:35
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

orieg added 2 commits March 1, 2026 11:09
Replace the opaque serialize-everything judy_packed_value with a
tagged union that stores long/double/bool/null/string directly
(tags 0–5), falling back to php_var_serialize only for arrays and
objects (tag 255). This avoids the serialize/unserialize overhead
for the common case where INT_TO_PACKED stores scalar values.

Breaking: old serialized INT_TO_PACKED data is incompatible.
@orieg orieg force-pushed the optimize/packed-fastpath branch from 6780d56 to 91b5628 Compare March 1, 2026 19:10
@orieg
Copy link
Owner Author

orieg commented Mar 1, 2026

Rebased onto current main (clean, no conflicts). 165/165 tests pass.

Both Gemini review items were addressed in 6780d56 prior to this rebase and the threads are already marked resolved:

  • [high] judy_packed_value_size default case — now emits E_WARNING and returns 0 instead of sizeof(judy_packed_value)
  • [medium] Magic-number tags — replaced with judy_packed_tag enum (JUDY_TAG_LONG, JUDY_TAG_DOUBLE, JUDY_TAG_TRUE, JUDY_TAG_FALSE, JUDY_TAG_NULL, JUDY_TAG_STRING, JUDY_TAG_SERIALIZED) used throughout judy_pack_value, judy_unpack_value, and judy_packed_value_size

@orieg orieg merged commit 17299d2 into main Mar 1, 2026
13 checks passed
@orieg orieg deleted the optimize/packed-fastpath branch March 1, 2026 19:16
@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