perf: move EG() and CG() in ZTS builds into __thread storage - #23227
perf: move EG() and CG() in ZTS builds into __thread storage#23227henderkes wants to merge 31 commits into
Conversation
… packagers want to set the glibc tls surplus in their httpd package
…s are in TLS storage)
arnaud-lb
left a comment
There was a problem hiding this comment.
I like the --with-tsrm-tls-model idea.
Did a first pass, but I will take the time to review carefully.
b78839c to
5d9e202
Compare
henderkes
left a comment
There was a problem hiding this comment.
It's a bit of a pain to find these on mobile, so I'll pin just these two, but there's more.
It could eventually be reworked when all the symbols move directly into thread storage, but I've not even begun thinking about it. There's probably no point except for zend_ini_scanner_globals and what we still have in the front.
| #if defined(ZEND_WIN32) && !defined(LIBZEND_EXPORTS) | ||
| /* Windows can't dllexport __declspec(thread) symbols, so outside Zend each module | ||
| * keeps a per-module `void *` pointer and reaches EG/CG via the resource-id indirection. */ | ||
| # define ZEND_TSRMLS_CACHE_T void * |
There was a problem hiding this comment.
The symbol is referenced as just a void* on windows
|
Created the aarch64 global-dynamic first first, but I may as well look into teaching the JIT to take the address of _tsrm_ls_cache + offset instead of the mandatory load for a bit of JIT speedup. |
|
@arnaud-lb out of zend_ini_scanner_globals))
virtual_cwd_globals))
zend_signal_globals_t))
zend_gc_globals_size())
php_core_globals))
sapi_globals_struct))
zend_accel_globals))
zend_jit_globals))Which do you think would make sense moving too? gc globals probably and maybe virtual_cwd_globals? |
|
alloc_globals is likely the most accessed global as every emalloc/efree and related fetch |
|
I think I already have a branch open for the AG move... perhaps it makes more sense moving it here, though. Edit: my memory these days, it's already in. But I just realised we're keeping a useless write around. |
bb0529c to
3c81898
Compare
Proving too far outside my expertise. I think this is reviewable like this now, the JIT optimization could be done later in IR side to let us drop the Unless @dstogov would like to have a go at it. |
arnaud-lb
left a comment
There was a problem hiding this comment.
The changes look good in general, but it's a bit hard to follow. Possibly grouping abstractions in TSRM would help? Right now you need to have TSRM.*, zend.c, zend_globals_macros.h to understand what's going on.
The Windows changes make sense, but maybe @shivammathur can take a look as well?
ts_free_resources(), ts_free_thread(), ts_free_id() and ts_apply_for_id() matched entries with p->thread_id == tsrm_thread_id(). That is ambiguous in exactly the case the surrounding code exists to handle: a stale entry of a dead thread can carry the live thread's recycled id. Compare against the entry that tsrm_tls_get() hands out instead. The recycle path in ts_resource_ex() no longer runs the stale entry's destructors. It used to point the TLS cache at an entry whose native TLS block had died with its thread, so any destructor reaching for EG or CG read freed memory. Leaking the dead thread's module globals is the better trade; a child process that recycles thread ids gets respawned by the SAPI anyway. Hold the shutdown marker across the window so that signal handlers stay away from both the stale entry and its replacement. Also reset tls_key, the TSRM tables and TSRMLS_CACHE on shutdown.
f5c7bcc to
4c08c6c
Compare
From @iliaal. I'll try approaching this with global per-thread cleanup hooks, but leave persistent_list in EG and wire ts_free_thread to clean it up. |
…free_thread` at thread exit SAPI's should already been doing this on their own before, but the apache2handler doesn't get control over threads, so it didn't call ts_free_thread and therefore started leaking resources with the move of per-thread globals from heap storage to native __thread storage
replay of #22231
Moves EG and CG into __thread storage after all. We first moved them into constant offsets (#22287) from *_tsrm_ls_cache, but I couldn't find a way to stop gcc or clang from reloading _tsrm_ls_cache base pointer between function calls, leading to an extra pointer load once per function.
This eliminates the pointer load, making the access sequence to EG/CG just a single mov (x64) / mrs + add + ldr (aarch64) under local-exec. initial-exec likewise loses the pointer load so 3 -> 2 instructions (x64).
cc @arnaud-lb
What I'm adding here to counter the global-dynamic fallback slowdown is the option to explicitly opt-in to initial-exec, under the knowledge that host programs loading it will need to increase the static tls surplus. This is not an issue for package providers.
PS: Actually figured out that the explicit model choice doesn't happen for musl, not sure about IE (static tls surplus on musl?), but LE should work just fine. That's for another PR though.