-
Notifications
You must be signed in to change notification settings - Fork 8k
[RFC][PoC] Allow opcache to be built statically #18660
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
Opcache is both a Zend extension and a PHP module. When Opcache is built statically, the engine will attempt to initialize it as a PHP module, but will not initialize the Zend extension. Here I make Opcache a two-way hybrid extension [1] so that initializing it as a PHP module also initializes the Zend extension. [1] https://www.phpinternalsbook.com/php7/extensions_design/zend_extensions.html#hybrid-extensions
...`_tsrm_ls_cache' at 0x12fc3 in section `.text' failed" In order to get information about the _tsrm_ls_cache TLS variable, we emit ASM code with TLS-related relocations [1]. These relocations, once adjusted by the linker, give us the offset or address of the variable's TLS descriptor. Specifically, we use relocations meant for use in the "General Dynamic" model described in [1]. When building Opcache statically in a binary, the linker will attempt to optimize the General Dynamic code sequence to a more efficient one. Unfortunately, linkers will break as we don't use the exact code sequence they are expecting. Here I use a different approach to get information about the TLS variable: * Emit the exact code sequence expected by linker, so that linking works * Extract the information we want by inspecting the ASM code. If the linker did something we didn't expect, we fallback to a safer (but slower) mechanism. [1] https://www.akkadia.org/drepper/tls.pdf
13a234a
to
ba04798
Compare
I remember low level extension like xdebug, blackfire, taint and opcache had problems in the past when enabled at the same time. I am not sure whether thats still the case with the latest php versions.
Just want to leave this here in case it rings a bell for someone
SAPIs such as phpdbg may run multiple startup-shutdown cycles in the same process.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On my Windows the php.exe
has size of 128 KB but the php_opcache.dll
has size of 463 KB. This is 3.6x binary size increase which might be an issue for embedded devices running php.
While I love the idea, is it necessary to enable opcache by default? Seems to me that --enable-opcache
could be used to enable it statically and --enable-opcache=shared
would build the shared extension.
I don't know of a reason not to always enable opcache, but I wouldn't be surprised if one existed. I (unfortunately) still know hosting providers who categorically disable opcache, although I'm not sure of their reasons and if they're valid.
While I love the idea, is it necessary to enable opcache by default? Seems to me that
--enable-opcache
could be used to enable it statically and--enable-opcache=shared
would build the shared extension.I don't know of a reason not to always enable opcache, but I wouldn't be surprised if one existed. I (unfortunately) still know hosting providers who categorically disable opcache, although I'm not sure of their reasons and if they're valid.
Having opcache compiled statically doesn't magically enable it.
And those hosting providers are doing it wrong.
Having opcache compiled statically doesn't magically enable it. And those hosting providers are doing it wrong.
Let me quote arnaud here:
This makes it possible to build opcache statically (and always enables it).
Having opcache compiled statically doesn't magically enable it. And those hosting providers are doing it wrong.
Let me quote arnaud here:
This makes it possible to build opcache statically (and always enables it).
The extension always being enabled, does not mean that opcache functionality is enabled.
You can have opcache installed with a shared object, and yet not have any opcache functionality enabled (by setting opcache.enable=0
).
This is most notable in CLI, as if you don't set opcache.enable_cli=1
then you do not have opcache in your CLI script.
So even if the extension is always enabled, it does not mean that you must actually "use" opcache.
However, there isn't really a benefit in having opcache.enable=0
, and things have moved around between opcache and the engine before, be that the optimizer or opcache.fast_shutdown
.
Moreover, hosting providers not knowing what they are doing (which wouldn't be the first time) is not really an argument against this.
I misunderstood the kind of "enabled" arnaud meant then, thank you for the clarification!
and things have moved around between opcache and the engine before
Indeed. And the goal is for this to be easier and more reliable. Allowing OPcache to be loaded dynamically prevents this. See also: https://externals.io/message/127459#127493
@arnaud-lb , Does this mean Opcache.enable
INI will be set to 1 by default after 8.5 but not Opcache.enable_cli
?
Because I see that below settings are automatically updated for Opcache in my php built with beta1
PHP version
PHP 8.5.0beta1 (cli) (built: Aug 14 2025 22:20:47) (NTS DEBUG)
Copyright (c) The PHP Group
Zend Engine v4.5.0-dev, Copyright (c) Zend Technologies
with Zend OPcache v8.5.0beta1, Copyright (c), by Zend Technologies
Zend OPcache
opcache.blacklist_filename => no value => no value
opcache.dups_fix => Off => Off
opcache.enable => On => On
opcache.enable_cli => Off => Off
opcache.enable_file_override => Off => Off
opcache.error_log => no value => no value
opcache.file_cache => no value => no value
opcache.file_cache_consistency_checks => On => On
opcache.file_cache_only => Off => Off
opcache.file_cache_read_only => Off => Off
opcache.file_update_protection => 2 => 2
opcache.force_restart_timeout => 180 => 180
opcache.huge_code_pages => Off => Off
opcache.interned_strings_buffer => 8 => 8
opcache.jit => disable => disable
opcache.jit_bisect_limit => 0 => 0
opcache.jit_blacklist_root_trace => 16 => 16
opcache.jit_blacklist_side_trace => 8 => 8
opcache.jit_buffer_size => 64M => 64M
opcache.jit_debug => 0 => 0
opcache.jit_hot_func => 127 => 127
opcache.jit_hot_loop => 61 => 61
opcache.jit_hot_return => 8 => 8
opcache.jit_hot_side_exit => 8 => 8
opcache.jit_max_exit_counters => 8192 => 8192
opcache.jit_max_loop_unrolls => 8 => 8
opcache.jit_max_polymorphic_calls => 2 => 2
opcache.jit_max_recursive_calls => 2 => 2
opcache.jit_max_recursive_returns => 2 => 2
opcache.jit_max_root_traces => 1024 => 1024
opcache.jit_max_side_traces => 128 => 128
opcache.jit_max_trace_length => 1024 => 1024
opcache.jit_prof_threshold => 0.005 => 0.005
opcache.lockfile_path => /tmp => /tmp
opcache.log_verbosity_level => 1 => 1
opcache.max_accelerated_files => 10000 => 10000
opcache.max_file_size => 0 => 0
opcache.max_wasted_percentage => 5 => 5
opcache.memory_consumption => 128 => 128
opcache.opt_debug_level => 0 => 0
opcache.optimization_level => 0x7FFEBFFF => 0x7FFEBFFF
opcache.preferred_memory_model => no value => no value
opcache.preload => no value => no value
opcache.preload_user => no value => no value
opcache.protect_memory => Off => Off
opcache.record_warnings => Off => Off
opcache.restrict_api => no value => no value
opcache.revalidate_freq => 2 => 2
opcache.revalidate_path => Off => Off
opcache.save_comments => On => On
opcache.use_cwd => On => On
opcache.validate_permission => Off => Off
opcache.validate_root => Off => Off
opcache.validate_timestamps => On => On
that's how it was before, it didn't change
remi sets enable_cli to 1 by default though, if you're using fedora/redhat
@arnaud-lb @henderkes .. I am not sure if this is the behavior when we build php from source.
This is below phpinfo I have on php 8.4.10 where I dont see any INI settings enabled for Opcache.
php -v
PHP 8.4.10 (cli) (built: Aug 14 2025 22:30:52) (NTS DEBUG)
Copyright (c) The PHP Group
Zend Engine v4.4.10, Copyright (c) Zend Technologies
# php -i |grep opcache
Configure Command => './configure' '--prefix=/usr/local' '--disable-phpdbg' '--enable-debug' '--enable-embed' '--enable-fpm' '--enable-intl' '--enable-json' '--enable-mbstring' '--enable-mysqlnd' '--enable-opcache' '--enable-pcntl' '--enable-pdo' '--enable-soap' '--enable-sockets' '--with-config-file-path=/usr/local/etc/php' '--with-config-file-scan-dir=/usr/local/etc/php/conf.d' '--with-curl' '--with-gnu-ld' '--with-ldap' '--with-mysqli=mysqlnd' '--with-openssl' '--with-pcre-valgrind' '--with-pdo-mysql=mysqlnd' '--with-pdo-sqlite' '--with-pear' '--with-sqlite3' '--with-valgrind' '--with-zlib' 'PKG_CONFIG_PATH=' 'EXTENSION_DIR=/usr/local/php/modules'
PHP_PARAMETERS => --disable-phpdbg --enable-debug --enable-embed --enable-fpm --enable-intl --enable-json --enable-mbstring --enable-mysqlnd --enable-opcache --enable-pcntl --enable-pdo --enable-soap --enable-sockets --with-config-file-path=/usr/local/etc/php --with-config-file-scan-dir=/usr/local/etc/php/conf.d --with-curl --with-gnu-ld --with-ldap --with-mysqli=mysqlnd --with-openssl --with-pcre-valgrind --with-pdo-mysql=mysqlnd --with-pdo-sqlite --with-pear --with-sqlite3 --with-valgrind --with-zlib
$_SERVER['PHP_PARAMETERS'] => --disable-phpdbg --enable-debug --enable-embed --enable-fpm --enable-intl --enable-json --enable-mbstring --enable-mysqlnd --enable-opcache --enable-pcntl --enable-pdo --enable-soap --enable-sockets --with-config-file-path=/usr/local/etc/php --with-config-file-scan-dir=/usr/local/etc/php/conf.d --with-curl --with-gnu-ld --with-ldap --with-mysqli=mysqlnd --with-openssl --with-pcre-valgrind --with-pdo-mysql=mysqlnd --with-pdo-sqlite --with-pear --with-sqlite3 --with-valgrind --with-zlib
$_ENV['PHP_PARAMETERS'] => --disable-phpdbg --enable-debug --enable-embed --enable-fpm --enable-intl --enable-json --enable-mbstring --enable-mysqlnd --enable-opcache --enable-pcntl --enable-pdo --enable-soap --enable-sockets --with-config-file-path=/usr/local/etc/php --with-config-file-scan-dir=/usr/local/etc/php/conf.d --with-curl --with-gnu-ld --with-ldap --with-mysqli=mysqlnd --with-openssl --with-pcre-valgrind --with-pdo-mysql=mysqlnd --with-pdo-sqlite --with-pear --with-sqlite3 --with-valgrind --with-zlib
HeenaBansal20
commented
Aug 18, 2025
@arnaud-lb @henderkes ,
There is one more difference I observed in PHP 8.4 and PHP 8.5 is about opcache_get_status()
.
calling this method in PHP 8.4 built from source with --enable-opcache
but no INI settings related to Opcache in php -I output returned
Fatal error: Uncaught Error: Call to undefined function opcache_get_status() in /opt/instana/src/opcache_get_status.php:2
Stack trace:
#0 {main}
thrown in /opt/instana/src/opcache_get_status.php on line 2
However , PHP 8.5 returned bool(false).
Which indicates that EG(function_table) has entry for opcache_get_status in PHP 8.5 , whereas in PHP 8.4 it is not under the EG function table.
Your php 8.4 installation doesn't have opcache loaded. Before this very issue it was always built as a shared extension, so you need to load it with zend_extension=opcache
Uh oh!
There was an error while loading. Please reload this page.
This makes it possible to build opcache statically (and always enables it).
JIT/ZTS is supported.
Summary of changes:
_tsrm_ls_cache
thread-specific variable in JIT, because it broke when opcache was linked to the executable.See commits for details.
RFC: https://wiki.php.net/rfc/make_opcache_required