iliaal / lchash
String-keyed hash table extension for PHP, backed by vendored klib khash.
Package info
Language:C
Type:php-ext
Ext name:ext-lchash
pkg:composer/iliaal/lchash
Requires
- php: >=7.4
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A small PHP extension that provides a string-keyed hash table backed by
klib khash. It has two APIs:
four procedural functions for a single per-request table (the original
2005 API), and an LcHash class with $obj[$key] access for any number
of per-instance tables.
Supports PHP 7.4 through 8.5, NTS and ZTS, on glibc Linux, musl, macOS, *BSD, and Windows.
lchash first shipped on PECL in 2005. The 1.0.0 release rewrites it for PHP 7.4 and later.
When to use this (and when not to)
Don't use it for speed. PHP arrays are faster on insert and lookup at
every size we measured. These numbers come from bench/bench.php on a
release build of PHP 8.4 NTS (glibc Linux x86_64, -O2):
| N entries | Insert (array) | Insert (lchash proc) | Insert (lchash OO) | Lookup (array) | Lookup (lchash proc) | Lookup (lchash OO) | Mem (array) | Mem (lchash) |
|---|---|---|---|---|---|---|---|---|
| 10,000 | 0.001s | 0.001s | 0.000s | 0.000s | 0.001s | 0.000s | 0.63 MB | 0.25 MB |
| 100,000 | 0.007s | 0.014s | 0.009s | 0.002s | 0.009s | 0.005s | 5.00 MB | 2.03 MB |
| 1,000,000 | 0.111s | 0.161s | 0.185s | 0.052s | 0.102s | 0.101s | 40.0 MB | 32.5 MB |
To reproduce, run php -d extension=$(pwd)/modules/lchash.so bench/bench.php <N>.
At 1M entries, PHP arrays are 1.4x to 1.7x faster on insert and 2x faster on lookup. PHP's HashTable uses a packed bucket layout with inlined zvals and opcode-level array-access specialization, none of which an extension can use.
lchash uses less memory than PHP arrays at every size: about 40% of the array's memory at 10k entries and about 80% at 1M. Keys and values are refcount-shared zend_strings with no per-entry Bucket overhead.
Reasons to use lchash:
- Memory-tight workloads. A long-running CLI worker that holds hundreds of thousands of small string mappings uses less RAM than with arrays.
- Porting C code. If you're migrating a C codebase that uses POSIX
hsearch_r, the procedural API's first-writer-wins semantics match glibchsearch(ENTER). - Legacy compatibility. The four function names are unchanged from the 2005 PECL release, for codebases that depend on them.
- Learning. It's a small PECL extension with one vendored header, easy to read if you're learning PHP extension development.
For most code, use a PHP array.
Install
PIE (recommended on PHP 8.x)
PIE is the PHP Foundation's successor to
PECL. It installs from Packagist and builds against the active
php-config.
pie install iliaal/lchash
Then add extension=lchash to your php.ini.
PECL
The package is still on the PECL channel:
pecl install lchash
From source
phpize ./configure --enable-lchash make make install
Windows
Every release has
pre-built .dll zips for PHP 8.3 / 8.4 / 8.5 × x64 / x86 × NTS / TS.
Download the matching zip, extract php_lchash.dll into your ext/
directory, and add extension=lchash to php.ini.
API
Procedural (single per-request table)
lchash_create(int $n_entries): bool lchash_destroy(): bool lchash_insert(string $key, string $value): bool lchash_find(string $key): string|false
Object-oriented (multiple per-instance tables)
$lc = new LcHash(int $n_entries = 1048576); $lc[$key] = $value; // write_dimension, last writer wins $value = $lc[$key]; // read_dimension, returns null on miss isset($lc[$key]); // has_dimension unset($lc[$key]); // unset_dimension
Semantics
Both APIs:
n_entriesis capped at 1,048,576 (1<<20).- Keys and values may contain any bytes, including NUL. Comparison is length-aware.
- Keys must be non-empty.
Procedural API:
- One table per request. Calling
lchash_create()twice withoutlchash_destroy()in between emits a warning and returnsfalse. If you don't calllchash_destroy(), the table is freed at request shutdown. - First writer wins. Inserting an existing key returns
trueand keeps the old value, like glibchsearch(ENTER). - Errors emit
E_WARNINGand returnfalse, for compatibility with the 2005 API.
OO API:
- Each
LcHashinstance has its own table, allocated on first write and freed with the object. - Last writer wins.
$lc[$key] = $valueoverwrites an existing key, as with PHP arrays. - Errors throw
Error(capacity exceeded, empty key, and so on).
Backend
lchash uses one backend on every platform: a vendored copy of
klib khash (header-only,
MIT-licensed) in khash.h. It has no external dependencies and no
build-time probes.
Both APIs hash keys with PHP's DJBX33A (zend_string_hash_val), so
collision-DoS exposure is the same as for PHP arrays. klib's
open-addressing layout degrades slightly more gracefully than chained
buckets under heavy collision.
License
PHP License 3.01 for the extension, MIT for the vendored
khash.h (header carries the full notice).