Skip to content

Commit 51cedbd

Browse files
committed
[Proposal] opcache.no_cache: Opcode optimization without caching
Currently, it isn't possible to enable optimizations without enabling caching. They should be orthogonal features - it's already possible to cache without optimization passes (`opcache.optimization_level=0`) Being forced to either enable caching or file_cache_only causes these problems: - For file_cache_only, users would be forced to manage the file cache, and users may not be confident in properly handling all potential edge cases. (Running out of disk space, need to clear stale entries, concerns about opcode corruption not being fixed after restarting a process, etc) - The shared memory block uses up a lot of RAM when individual PHP processes are known to be long-lived and don't have a common memory address space. (e.g. multiple long-lived php scripts managed by something that is not a php script (e.g. `supervisord`, daemons, tools run in the background in IDEs, etc)) The opcodes are duplicated in shmem, which wastes memory in use cases where they're stored in the cache but never retrieved. The default for opcache.memory_consumption is 128M. Even when this isn't used, the virtual memory to track the shared memory(shmem) segment seems to add 2MB extra per **independent** php process in "shared memory" segments, reducing the free RAM available for other processes. (starting a large number of php CLI scripts that sleep() in a loop, `free` (linux program to report free memory) reports that `shared` increases by 2MB per process without opcache.no_cache, but barely increases with opcache.no_cache=1 On an unrelated PR php#5097 (comment) dstogov mentioned that > Also, it would be great to move optimizer and JIT into core, to make them > available even without opcode caching. This PR is one potential approach for making the optimizer and JIT available without opcode caching. This is currently a proof of concept - feel free to point out combinations of settings that should be rejected. Potential areas for future work: - Normally, opcache optimizes a file based only on that one file's contents. When `opcache.no_cache=1` is used, it may be possible to use all of the class, function, constant, etc. definitions parsed from previously parsed files (to eliminate dead code, inline function calls, etc).
1 parent 968c31a commit 51cedbd

File tree

3 files changed

+67
-13
lines changed

3 files changed

+67
-13
lines changed

ext/opcache/ZendAccelerator.c

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ zend_bool accel_startup_ok = 0;
114114
static char *zps_failure_reason = NULL;
115115
char *zps_api_failure_reason = NULL;
116116
zend_bool file_cache_only = 0; /* process uses file cache only */
117+
zend_bool no_cache = 0; /* process does not use any cache */
117118
#if ENABLE_FILE_CACHE_FALLBACK
118119
zend_bool fallback_process = 0; /* process uses file cache fallback */
119120
#endif
@@ -468,7 +469,7 @@ zend_string* ZEND_FASTCALL accel_new_interned_string(zend_string *str)
468469
uint32_t pos, *hash_slot;
469470
zend_string *s;
470471

471-
if (UNEXPECTED(file_cache_only)) {
472+
if (UNEXPECTED(file_cache_only || no_cache)) {
472473
return str;
473474
}
474475

@@ -1886,6 +1887,28 @@ zend_op_array *file_cache_compile_file(zend_file_handle *file_handle, int type)
18861887
return op_array;
18871888
}
18881889

1890+
// Optimize opcodes without caching the file in shared memory or in disk
1891+
zend_op_array *no_cache_compile_file(zend_file_handle *file_handle, int type)
1892+
{
1893+
zend_op_array *op_array = NULL;
1894+
zend_persistent_script *persistent_script; // not actually persistent with opcache.no_cache.
1895+
1896+
if (is_stream_path(file_handle->filename) &&
1897+
!is_cacheable_stream_path(file_handle->filename)) {
1898+
return accelerator_orig_compile_file(file_handle, type);
1899+
}
1900+
1901+
// Take the same code path as if attempting to save a blacklisted script to file cache.
1902+
persistent_script = opcache_compile_file(file_handle, type, NULL, &op_array);
1903+
if (persistent_script) {
1904+
// Attempt to optimize the script before returning it.
1905+
zend_optimize_script(&persistent_script->script, ZCG(accel_directives).optimization_level, ZCG(accel_directives).opt_debug_level);
1906+
1907+
return zend_accel_load_script(persistent_script, /* from_memory */ 0);
1908+
}
1909+
return op_array;
1910+
}
1911+
18891912
int check_persistent_script_access(zend_persistent_script *persistent_script)
18901913
{
18911914
char *phar_path, *ptr;
@@ -1922,15 +1945,23 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type)
19221945
ZCG(cache_opline) = NULL;
19231946
ZCG(cache_persistent_script) = NULL;
19241947
if (file_handle->filename
1925-
&& ZCG(accel_directives).file_cache
19261948
&& ZCG(enabled) && accel_startup_ok) {
1927-
return file_cache_compile_file(file_handle, type);
1949+
if (ZCG(accel_directives).file_cache) {
1950+
return file_cache_compile_file(file_handle, type);
1951+
} else if (no_cache) {
1952+
return no_cache_compile_file(file_handle, type);
1953+
}
19281954
}
19291955
return accelerator_orig_compile_file(file_handle, type);
19301956
} else if (file_cache_only) {
19311957
ZCG(cache_opline) = NULL;
19321958
ZCG(cache_persistent_script) = NULL;
19331959
return file_cache_compile_file(file_handle, type);
1960+
} else if (no_cache) {
1961+
// TODO: Why is opcache_get_status using accelerator_enabled instead of enabled?
1962+
ZCG(cache_opline) = NULL;
1963+
ZCG(cache_persistent_script) = NULL;
1964+
return no_cache_compile_file(file_handle, type);
19341965
} else if (!ZCG(accelerator_enabled) ||
19351966
(ZCSG(restart_in_progress) && accel_restart_is_active())) {
19361967
if (ZCG(accel_directives).file_cache) {
@@ -2251,7 +2282,7 @@ static int persistent_stream_open_function(const char *filename, zend_file_handl
22512282
/* zend_resolve_path() replacement for PHP 5.3 and above */
22522283
static zend_string* persistent_zend_resolve_path(const char *filename, size_t filename_len)
22532284
{
2254-
if (!file_cache_only &&
2285+
if (!file_cache_only && !no_cache &&
22552286
ZCG(accelerator_enabled)) {
22562287

22572288
/* check if callback is called from include_once or it's a main request */
@@ -2375,7 +2406,7 @@ int accel_activate(INIT_FUNC_ARGS)
23752406
ZCG(cwd_key_len) = 0;
23762407
ZCG(cwd_check) = 1;
23772408

2378-
if (file_cache_only) {
2409+
if (file_cache_only || no_cache) {
23792410
ZCG(accelerator_enabled) = 0;
23802411
return SUCCESS;
23812412
}
@@ -2931,7 +2962,8 @@ static int accel_post_startup(void)
29312962
/* End of non-SHM dependent initializations */
29322963
/********************************************/
29332964
file_cache_only = ZCG(accel_directives).file_cache_only;
2934-
if (!file_cache_only) {
2965+
no_cache = ZCG(accel_directives).no_cache;
2966+
if (!file_cache_only && !no_cache) {
29352967
size_t shm_size = ZCG(accel_directives).memory_consumption;
29362968
#ifdef HAVE_JIT
29372969
size_t jit_size = 0;
@@ -3023,10 +3055,14 @@ static int accel_post_startup(void)
30233055
zend_shared_alloc_unlock();
30243056

30253057
SHM_PROTECT();
3026-
} else if (!ZCG(accel_directives).file_cache) {
3058+
} else if (file_cache_only && !ZCG(accel_directives).file_cache) {
30273059
accel_startup_ok = 0;
30283060
zend_accel_error(ACCEL_LOG_FATAL, "opcache.file_cache_only is set without a proper setting of opcache.file_cache");
30293061
return SUCCESS;
3062+
} else if (no_cache && ZCG(accel_directives).file_cache) {
3063+
accel_startup_ok = 0;
3064+
zend_accel_error(ACCEL_LOG_FATAL, "opcache.no_cache cannot be combined with opcache.file_cache");
3065+
return SUCCESS;
30303066
} else {
30313067
accel_shared_globals = calloc(1, sizeof(zend_accel_shared_globals));
30323068

@@ -3083,7 +3119,7 @@ static int accel_post_startup(void)
30833119

30843120
zend_optimizer_startup();
30853121

3086-
if (!file_cache_only && ZCG(accel_directives).interned_strings_buffer) {
3122+
if (!file_cache_only && !no_cache && ZCG(accel_directives).interned_strings_buffer) {
30873123
accel_use_shm_interned_strings();
30883124
}
30893125

@@ -3101,6 +3137,7 @@ void accel_shutdown(void)
31013137
{
31023138
zend_ini_entry *ini_entry;
31033139
zend_bool _file_cache_only = 0;
3140+
zend_bool _no_cache = 0;
31043141

31053142
#ifdef HAVE_JIT
31063143
zend_jit_shutdown();
@@ -3122,15 +3159,16 @@ void accel_shutdown(void)
31223159
}
31233160

31243161
_file_cache_only = file_cache_only;
3162+
_no_cache = no_cache;
31253163

31263164
accel_reset_pcre_cache();
31273165

31283166
#ifdef ZTS
31293167
ts_free_id(accel_globals_id);
31303168
#endif
31313169

3132-
if (!_file_cache_only) {
3133-
/* Delay SHM detach */
3170+
if (!_file_cache_only && !_no_cache) {
3171+
/* Delay SHM detach - this only needs to be done if SHM is used */
31343172
orig_post_shutdown_cb = zend_post_shutdown_cb;
31353173
zend_post_shutdown_cb = accel_post_shutdown;
31363174
}
@@ -4747,6 +4785,10 @@ static int accel_finish_startup(void)
47474785
zend_accel_error(ACCEL_LOG_WARNING, "Preloading doesn't work in \"file_cache_only\" mode");
47484786
return SUCCESS;
47494787
}
4788+
if (UNEXPECTED(no_cache)) {
4789+
zend_accel_error(ACCEL_LOG_WARNING, "Preloading doesn't work in \"no_cache\" mode");
4790+
return SUCCESS;
4791+
}
47504792

47514793
/* exclusive lock */
47524794
zend_shared_alloc_lock();

ext/opcache/ZendAccelerator.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ typedef struct _zend_accel_directives {
177177
#endif
178178
char *file_cache;
179179
zend_bool file_cache_only;
180+
zend_bool no_cache;
180181
zend_bool file_cache_consistency_checks;
181182
#if ENABLE_FILE_CACHE_FALLBACK
182183
zend_bool file_cache_fallback;
@@ -290,6 +291,7 @@ extern char accel_uname_id[32];
290291
#endif
291292
extern zend_bool accel_startup_ok;
292293
extern zend_bool file_cache_only;
294+
extern zend_bool no_cache;
293295
#if ENABLE_FILE_CACHE_FALLBACK
294296
extern zend_bool fallback_process;
295297
#endif

ext/opcache/zend_accelerator_module.c

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ ZEND_INI_BEGIN()
240240
#if ENABLE_FILE_CACHE_FALLBACK
241241
STD_PHP_INI_ENTRY("opcache.file_cache_fallback" , "1" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.file_cache_fallback, zend_accel_globals, accel_globals)
242242
#endif
243+
STD_PHP_INI_ENTRY("opcache.no_cache" , "0" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.no_cache, zend_accel_globals, accel_globals)
243244
#ifdef HAVE_HUGE_CODE_PAGES
244245
STD_PHP_INI_BOOLEAN("opcache.huge_code_pages" , "0" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.huge_code_pages, zend_accel_globals, accel_globals)
245246
#endif
@@ -337,6 +338,10 @@ void zend_accel_override_file_functions(void)
337338
zend_accel_error(ACCEL_LOG_WARNING, "file_override_enabled has no effect when file_cache_only is set");
338339
return;
339340
}
341+
if (no_cache) {
342+
zend_accel_error(ACCEL_LOG_WARNING, "file_override_enabled has no effect when file_cache_only is set");
343+
return;
344+
}
340345
/* override file_exists */
341346
if ((old_function = zend_hash_str_find_ptr(CG(function_table), "file_exists", sizeof("file_exists")-1)) != NULL) {
342347
orig_file_exists = old_function->internal_function.handler;
@@ -366,7 +371,7 @@ void zend_accel_info(ZEND_MODULE_INFO_FUNC_ARGS)
366371
{
367372
php_info_print_table_start();
368373

369-
if (ZCG(accelerator_enabled) || file_cache_only) {
374+
if ((ZCG(accelerator_enabled) || file_cache_only) && !no_cache) {
370375
php_info_print_table_row(2, "Opcode Caching", "Up and Running");
371376
} else {
372377
php_info_print_table_row(2, "Opcode Caching", "Disabled");
@@ -376,7 +381,7 @@ void zend_accel_info(ZEND_MODULE_INFO_FUNC_ARGS)
376381
} else {
377382
php_info_print_table_row(2, "Optimization", "Disabled");
378383
}
379-
if (!file_cache_only) {
384+
if (!file_cache_only && !no_cache) {
380385
php_info_print_table_row(2, "SHM Cache", "Enabled");
381386
} else {
382387
php_info_print_table_row(2, "SHM Cache", "Disabled");
@@ -395,7 +400,7 @@ void zend_accel_info(ZEND_MODULE_INFO_FUNC_ARGS)
395400
#else
396401
php_info_print_table_row(2, "JIT", "Not Available");
397402
#endif
398-
if (file_cache_only) {
403+
if (file_cache_only || no_cache) { // TODO test failure mode
399404
if (!accel_startup_ok || zps_api_failure_reason) {
400405
php_info_print_table_row(2, "Startup Failed", zps_api_failure_reason);
401406
} else {
@@ -546,6 +551,10 @@ ZEND_FUNCTION(opcache_get_status)
546551
add_assoc_bool(return_value, "file_cache_only", 1);
547552
return;
548553
}
554+
if (no_cache) {
555+
add_assoc_bool(return_value, "no_cache", 1);
556+
return;
557+
}
549558

550559
add_assoc_bool(return_value, "cache_full", ZSMMG(memory_exhausted));
551560
add_assoc_bool(return_value, "restart_pending", ZCSG(restart_pending));
@@ -707,6 +716,7 @@ ZEND_FUNCTION(opcache_get_configuration)
707716
#if ENABLE_FILE_CACHE_FALLBACK
708717
add_assoc_bool(&directives, "opcache.file_cache_fallback", ZCG(accel_directives).file_cache_fallback);
709718
#endif
719+
add_assoc_bool(&directives, "opcache.no_cache", ZCG(accel_directives).no_cache);
710720

711721
add_assoc_long(&directives, "opcache.file_update_protection", ZCG(accel_directives).file_update_protection);
712722
add_assoc_long(&directives, "opcache.opt_debug_level", ZCG(accel_directives).opt_debug_level);

0 commit comments

Comments
 (0)