iliaal / fast_uuid
Fast RFC 9562 UUID generation (v1/v2/v3/v4/v6/v7/v8 + nil/max) as a PHP C extension, with a ramsey/uuid-shaped object API and procedural fast-path functions.
Package info
Type:php-ext
Ext name:ext-fast_uuid
pkg:composer/iliaal/fast_uuid
Requires
- php: >=8.1
README
A high-performance PHP C extension for RFC 9562 / RFC 4122 UUID generation, 11x to 57x faster than ramsey/uuid on v1/v4/v7 generation and 3x to 5x faster on parsing. It produces versions 1, 2 (DCE Security), 3, 4, 5, 6, 7, 8, plus nil and max. The engine is pure C (no C++/libstdc++). The object API mirrors ramsey/uuid under the FastUuid namespace, and procedural functions give a zero-allocation fast path for the hottest call sites.
Full API reference with runnable examples: docs/index.html. Benchmarks: BENCHMARKS.md.
⚡ Why it's fast
- Batched CSPRNG:
getrandom()is amortized across ~500 v4s via an 8 KB per-thread buffer instead of one syscall per UUID. ramsey's per-callrandom_bytes()is the usual bottleneck. - No property table: the object is 16 inline bytes plus a lazily-cached canonical string. No
HashTable, no declared properties, custom create/free/clone/compare/cast handlers. - SIMD hex formatter: x86-64 uses a runtime-dispatched SSSE3
pshufb-LUT path, and ARM64 uses a NEON table-lookup path. Both turn 16 bytes into 32 hex in a handful of vector ops, with a scalar LUT fallback for other architectures. - Procedural path:
uuid_v4()and friends return azend_stringwith no object allocation, for ORM inserts and cache keys.
Requirements
- PHP 8.1 through 8.6, NTS or ZTS. PHP 8.1/8.2/8.3 build via small
#if PHP_VERSION_IDpolyfills. - x86-64 and ARM64 get the SIMD formatter automatically; other architectures fall back to the scalar path. No build flags needed either way.
- No external libraries. v1 and v6 use an internal RFC-compliant generator with a random node (multicast bit set, per RFC 9562 §5.1). v3 and v5 use PHP's bundled MD5/SHA1.
📦 Install
The quickest path is PIE, which resolves a prebuilt
binary for your platform (Windows x86/x64 NTS/TS, Linux glibc
x86_64/arm64, macOS arm64) and falls back to a source build otherwise:
pie install iliaal/fast_uuid
Then enable it with extension=fast_uuid in your php.ini.
🛠️ Build from source
phpize ./configure --enable-fast-uuid make make test php -d extension="$(pwd)/modules/fast_uuid.so" -r 'echo \FastUuid\Uuid::uuid4(), "\n"; echo uuid_v7(), "\n";'
The arginfo header is generated from fast_uuid.stub.php. To regenerate after editing the stub:
php /path/to/php-src/build/gen_stub.php fast_uuid.stub.php
Object API: FastUuid\Uuid
Static factories (all return FastUuid\UuidInterface):
uuid1(int|string|null $node = null, ?int $clockSeq = null)
uuid2(int $localDomain, int|string|null $localIdentifier = null, int|string|null $node = null, ?int $clockSeq = null)
uuid3(UuidInterface|string $ns, string $name)
uuid4()
uuid5(UuidInterface|string $ns, string $name)
uuid6(int|string|null $node = null, ?int $clockSeq = null)
uuid7(int|DateTimeInterface|null $dateTime = null) // int = unix milliseconds
uuid8(string $bytes) // 16 raw bytes
fromString(string $uuid) // canonical, urn:uuid:, {braced}, bare 32-hex, any case
fromBytes(string $bytes) // 16 raw bytes
fromInteger(string $integer) // decimal string
fromHexadecimal(string $hex) // 32 hex chars
fromDateTime(DateTimeInterface $dt, int|string|null $node = null, ?int $clockSeq = null)
isValid(string $uuid): bool
Instance methods:
toString(): string __toString(): string getBytes(): string getHex(): string
getUrn(): string getVersion(): ?int getVariant(): ?int getInteger(): string
getDateTime(): DateTimeImmutable getFields(): array equals(mixed): bool
compareTo(mixed): int jsonSerialize(): string getTimestampMillis(): int
toBytes(): string toHexadecimal(): string toUrn(): string toInteger(): string
toBytes()/toHexadecimal()/toUrn()/toInteger()are aliases ofgetBytes()/getHex()/getUrn()/getInteger(), matching theget*→to*naming of the newerramsey/identifierlibrary.getTimestampMillis()returns the embedded timestamp as unix milliseconds for the time-based versions (v1, v2, v6, v7) and is much cheaper thangetDateTime()since it builds no object; it throwsUnsupportedOperationExceptionfor v3/v4/v5/v8.Uuid::uuid7()accepts a unix-millisecondintas well as aDateTimeInterface, which skips the DateTime machinery entirely. The proceduraluuid_v7_at(int $unixMillis)is the fastest explicit-timestamp form.- UUIDv7 carries sub-millisecond precision (RFC 9562 §6.2 Method 3): the sub-ms fraction is encoded in
rand_aand a monotonic counter lives inrand_b, so v7s generated within the same millisecond still sort in time order.getDateTime()reads back at millisecond precision, matchingramsey/uuid. getVariant()returns0(NCS),2(RFC 4122),6(Microsoft),7(future);getVersion()isnullfor nil/max.getDateTime()works for the time-based versions (v1, v2, v6, v7) and throwsFastUuid\Exception\UnsupportedOperationExceptionfor v3/v4/v5/v8.getFields()returns an associative array of hex strings (time_low,time_mid,time_hi_and_version,clock_seq_hi_and_reserved,clock_seq_low,node). For the ramsey-shapedFieldsInterface/Typeobjects, use the compat layer below.equals()accepts another UUID object or its canonical string.
Constants: NIL, MAX, NAMESPACE_DNS, NAMESPACE_URL, NAMESPACE_OID, NAMESPACE_X500, DCE_DOMAIN_PERSON, DCE_DOMAIN_GROUP, DCE_DOMAIN_ORG.
Implements FastUuid\UuidInterface, JsonSerializable, Stringable.
DCE Security (v2)
$u = \FastUuid\Uuid::uuid2(\FastUuid\Uuid::DCE_DOMAIN_PERSON); // local id auto-fills from POSIX uid $u->getVersion(); // 2 $u = \FastUuid\Uuid::uuid2(\FastUuid\Uuid::DCE_DOMAIN_GROUP, 4242);
The local identifier occupies bytes 0 to 3 (big-endian); the local domain is stored in byte 9. With domain PERSON or GROUP and a null identifier, the extension uses the process uid or gid.
Exceptions
FastUuid\Exception\InvalidArgumentException(extends\InvalidArgumentException): a bad length, node, or integer.FastUuid\Exception\InvalidUuidStringException(extends the above): an unparseable UUID string.FastUuid\Exception\UnsupportedOperationException(extends\RuntimeException): raised bygetDateTime()on a non-time-based version.
Out-of-range factory inputs are rejected, not silently truncated: a v7 timestamp past the 48-bit millisecond field, a fromDateTime instant outside the v1 Gregorian window, a node outside 0..2^48-1, a clock sequence outside 0..0x3fff, or uuid2 without an explicit local identifier for a non-PERSON/GROUP domain all throw InvalidArgumentException.
Procedural API
uuid_v1() uuid_v3($ns, $name) uuid_v4() uuid_v4_fast() uuid_v5($ns, $name) uuid_v6() uuid_v7() uuid_v8($bytes)
uuid_v7_at($unixMillis) // v7 from a unix-millisecond int (no DateTime)
uuid_to_bin($uuid) // canonical/parsed string -> 16 raw bytes
uuid_from_bin($bytes)// 16 raw bytes -> canonical string
uuid_is_valid($uuid) // bool
fast_uuid_random_bytes($length) // batched CSPRNG bytes, $length > 0
uuid_v4_fast() uses a non-cryptographic xoshiro256** PRNG. Use it only for non-security IDs.
ramsey/uuid compatibility layer (FastUuid\Compat)
compat/ is a PSR-4 (FastUuid\Compat\) companion package (iliaal/fast-uuid-compat) that provides the cold-path ramsey ergonomics on top of the C engine. It ships in this repo's compat/ directory and is not on Packagist yet; install it as a Composer path repository (composer config repositories.fast-uuid-compat path /path/to/fast_uuid/compat && composer require iliaal/fast-uuid-compat:@dev) or autoload FastUuid\Compat\ to compat/src/. It provides: UuidFactory, the per-version Rfc4122\UuidV1…UuidV8 / NilUuid / MaxUuid / Nonstandard\Uuid classes, Rfc4122\UuidV2 with getLocalDomain() / getLocalIdentifier(), Rfc4122\Fields (FieldsInterface), Type\Hexadecimal, Type\Integer, the codecs (StringCodec, OrderedTimeCodec, TimestampFirstCombCodec, TimestampLastCombCodec, GuidStringCodec), Guid\Guid, the providers (RandomGeneratorInterface, NodeProviderInterface, TimeGeneratorInterface + defaults), and the validators (GenericValidator, NonstandardValidator).
Generation stays on the pure-C fast path; supplying a custom RandomGeneratorInterface / TimeGeneratorInterface / NodeProviderInterface intentionally routes off it (ramsey behaviour) so application-supplied generators win. Migration from ramsey/uuid is largely a use swap from Ramsey\Uuid\Uuid to FastUuid\Compat\Uuid. The compat package has no external dependencies beyond the extension itself.
📊 Benchmarks
Throughput against ramsey/uuid 4.9.2 and the PECL uuid extension 1.3.0
(libuuid-backed). PHP 8.4.22 NTS, non-debug, no sanitizers; SSSE3 hex formatter
active (x86-64). Each operation runs 300,000 iterations after a 20,000-iteration
warmup; reported figure is the best of 40 runs. Million ops/sec, higher is
better:
| Operation | fast_uuid (obj) | fast_uuid (proc) | ramsey/uuid | PECL uuid |
|---|---|---|---|---|
| v4 gen→string | 12.6 | 19.5 | 1.10 | 0.47 |
| v1 gen→string | 12.3 | 16.5 | 0.29 | 8.22 |
| v7 gen→string | 12.1 | 19.8 | 0.66 | n/a |
| parse→16 bytes | 10.4 | 16.2 | 3.18 | 5.28 |
Speedup over ramsey/uuid: v4 11.5x to 17.7x, v1 42x to 57x, v7 18.3x to 30x,
parse 3.3x to 5.1x.
The fast_uuid operations are fast enough (~50 ns) that scheduler noise
dominates a single run, so read the fast_uuid columns as order-of-magnitude,
not three-significant-digit (roughly ±10% run-to-run). ramsey/uuid (~900 ns)
and PECL (~2 µs) reproduce to within ~3%. Full table, the ARM64/NEON numbers,
the timestamp/DateTime API breakdown, and how to reproduce are in
BENCHMARKS.md.
Testing
make test # run-tests.php against the built .so
The suite (tests/*.phpt) covers every version, all parse forms, per-version getDateTime, fields/integer, node/clockSeq, the exception hierarchy, the procedural functions, the SIMD formatter, and the full compat layer. Verified green on PHP 8.1 / 8.2 / 8.3 / 8.4 / 8.4-ZTS / 8.5 / 8.6 (0 compiler warnings) and clean under an ASan/UBSan-instrumented build.
Contributing
Build instructions, the stub-to-arginfo workflow, and the test conventions are in CONTRIBUTING.md. Run the suite against more than one PHP version when you touch C, and add an ASan/UBSan run for any change to a parse, format, or generation path.
Security
Report a vulnerability by email to ilia@ilia.ws. Details and scope are in SECURITY.md.
🔗 PHP Performance Toolkit
Companion native PHP extensions for high-throughput PHP workloads:
- php_excel: native Excel I/O. 7-10x faster than PhpSpreadsheet, full XLS/XLSX with formulas, formatting, and styling. Powered by LibXL.
- mdparser: native CommonMark + GFM parser. 15-30x faster than pure-PHP alternatives, 652/652 spec examples pass.
- php_clickhouse: native ClickHouse client speaking the wire protocol directly. Picks up where SeasClick left off.
- fastchart: native chart-rendering extension. 26 chart types behind one fluent OO API, SVG-canonical with PNG/JPG/WebP output (no libgd dependency).
- fastjson: drop-in faster
ext/json, backed by yyjson. 6x encode, 2.7x decode, 5x validate. - phpser: decoder-optimized binary serializer for cache workloads. Faster than igbinary on packed numerics and DTO batches.
License
BSD-3-Clause. See LICENSE.
Follow @iliaa on X • Blog • If this sped up your UUID generation, ⭐ star it!
