-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Improve performance of WeakReference/WeakMap. Avoid hash collisions on pointers. #7690
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
As seen in #7672 - php's WeakReference/WeakMap handling is spending a lot of time in hash table lookups/modifications. This improves performance by:
The Travis build failure is unrelated. The azure failures are caused by running out of CI minutes. |
cc @bwoebi as the new commit changes keys for the zend_weakrefs_hash_add/del API -- they're no longer simply zend_object* cast to integer. Is that okay? It shouldn't matter if your just use zend_weakrefs_hash_add/del, but may matter is you use ZEND_HASH_FOREACH and interpret the keys. |
I assume implementations that needed to inspect internals could add macros like these - 8.2 alphas aren't even out yet. If debuggers or instrumentation tools needed this I guess this could be added to the public API in 8.2. static zend_always_inline zend_ulong copyof_zend_object_ptr_to_weakref_key(const zend_object *object)
{
#if PHP_VERSION_ID >= 80200
ZEND_ASSERT(((uintptr_t)object) % ZEND_MM_ALIGNMENT == 0);
return ((uintptr_t) object) >> ZEND_MM_ALIGNMENT_LOG2;
#else
return (zend_long) object;
#endif
}
static zend_always_inline zend_object *copyof_zend_weakref_key_to_zend_object_ptr(zend_ulong key)
{
#if PHP_VERSION_ID >= 80200
return (zend_object *) (((uintptr_t) key) << ZEND_MM_ALIGNMENT_LOG2);
#else
return (zend_long) object;
#endif
} |
I suppose that's fine, we just should have some macro in weakrefs.h to convert the bare key then. |
That's done |
ext/zend_test/tests/zend_weakmap.phpt is failing. |
0e762b1
to
bd94051
Compare
I'd missed that since I thought php was still out of Azure Pipelines minutes (thanks for fixing that). That's fixed now, it needed to use the new function in zend_weakref.h to convert the hash key to a pointer. |
This makes reading/writing with `$splObjectStorage[$offset]` shorthand twice as fast as it was previously, and a bit faster than offsetGet/offsetSet instead of (previously) much slower. Related to phpGH-7690
This makes reading/writing with `$splObjectStorage[$offset]` shorthand twice as fast as it was previously, and a bit faster than offsetGet/offsetSet instead of (previously) much slower. Related to phpGH-7690
Shift pointers by ZEND_MM_ALIGNMENT_LOG2 to avoid the noticeable performance degradation caused by hash table collisions. in `EG(weakrefs)` and zend_weakmap->ht On 64-bit platforms, pointers are usually aligned to at least 8 bytes, so only one in 8 hash buckets were actually getting used. (With the metadata needed to track allocations, alignment might be at least 16 bytes in practice) Address review comments, add optimization Make it public for any extensions that need to work with EG(weakrefs) for instrumentation, debugging, etc. (e.g. zend_test) PHP 8.1 and below would use the raw pointer value as a hash key instead.
bd94051
to
1f5b8e9
Compare
This makes reading/writing with `$splObjectStorage[$offset]` shorthand twice as fast as it was previously, and a bit faster than offsetGet/offsetSet instead of (previously) much slower. Related to phpGH-7690
This makes reading/writing with `$splObjectStorage[$offset]` shorthand twice as fast as it was previously, and a bit faster than offsetGet/offsetSet instead of (previously) much slower. Call destructor after overriding old SplObjectStorage entry. Previously, it was called before, which was possibly unsafe if the destructor had side effects. Add tests. Related to phpGH-7690
This makes reading/writing with `$splObjectStorage[$offset]` shorthand twice as fast as it was previously, and a bit faster than offsetGet/offsetSet instead of (previously) much slower. Call destructor after overriding old SplObjectStorage entry. Previously, it was called before, which was possibly unsafe if the destructor had side effects. Add tests. Closes GH-7695 Related to GH-7690 Check for ref in SplObjectStorage->__unserialize, check for ref. SplObjectStorage->unserialize may be a different cause of references for malformed inputs, so continue checking In internally used methods, convert references to non-references if they're found.
Shift pointers by ZEND_MM_ALIGNMENT_LOG2
to avoid the noticeable performance degradation caused by hash table collisions.
in
EG(weakrefs)
andWeakMap
entriesOn 64-bit platforms, pointers are usually aligned to at least 8 bytes,
so only one in 8 hash buckets were actually getting used.
(With the metadata needed to track allocations,
alignment might be at least 16 bytes in practice)
Before this change with
taskset 1 sapi/cli/php -d memory_limit=2G -d zend_extension=$PWD/modules/opcache.so -d opcache.enable_cli=1 -d opcache.enable=1 bench.php
in a non-debug buildBenchmarking note: SplObjectStorage is slow because there's no native write handler (SplObjectStorage implementation was removed because it needed to call
offsetSet when subclasses overrid it and there were historically bugs), where it's the other way around for WeakMap which has a native write handler
(I forgot about that)
After this change in php 8.2, performance is comparable to SplObjectStorage(first run grows
EG(weakrefs)
and allocates memory for the first time and is slower:Note that EG(weakrefs) doesn't shrink capacity, so the first entry's memory is a more accurate representation of weakref memory usage for a long-running program.