Skip to content

Commit 2d75134

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`) (In use cases such as running a web server in apache, it's very desirable to both cache and optimize opcodes. For short-lived cli scripts, either file_cache or disabling opcache (to reduce optimization overhead) is often useful. In a few use cases, it's desirable to optimize without any caching) Being forced to either enable shared memory caching or file_cache_only causes these problems: - **For file_cache_only, users would be forced to manage the file cache.** - Users may not be confident in properly handling all potential edge cases. - End users of applications may not be familiar with opcache. (Concerns include running out of disk space, needing to clear stale entries, concerns about opcode corruption or opcodes for the wrong file contents 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 opcodes are 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 `opcache.no_cache` takes precedence over `opcache.file_cache*` settings. (If enabled, neither the shared memory cache nor the file cache will be used. It is an error to use both opcache.no_cache and opcache.preload.) 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. - Even if everything except caching was moved into core, It would still be useful to have a configurable way to disable opcode caching via a system ini setting (`php -d opcache.no_cache=1 my_script.php`) 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 25aa125 commit 2d75134

File tree

6 files changed

+139
-14
lines changed

6 files changed

+139
-14
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 (takes precedence over file_cache_only) */
117118
#if ENABLE_FILE_CACHE_FALLBACK
118119
zend_bool fallback_process = 0; /* process uses file cache fallback */
119120
#endif
@@ -469,7 +470,7 @@ zend_string* ZEND_FASTCALL accel_new_interned_string(zend_string *str)
469470
uint32_t pos, *hash_slot;
470471
zend_string *s;
471472

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

@@ -1947,6 +1948,28 @@ zend_op_array *file_cache_compile_file(zend_file_handle *file_handle, int type)
19471948
return op_array;
19481949
}
19491950

1951+
// Optimize opcodes without caching the file in shared memory or in disk
1952+
zend_op_array *no_cache_compile_file(zend_file_handle *file_handle, int type)
1953+
{
1954+
zend_op_array *op_array = NULL;
1955+
zend_persistent_script *persistent_script; // not actually persistent with opcache.no_cache.
1956+
1957+
if (is_stream_path(file_handle->filename) &&
1958+
!is_cacheable_stream_path(file_handle->filename)) {
1959+
return accelerator_orig_compile_file(file_handle, type);
1960+
}
1961+
1962+
// Take the same code path as if attempting to save a blacklisted script to file cache.
1963+
persistent_script = opcache_compile_file(file_handle, type, NULL, &op_array);
1964+
if (persistent_script) {
1965+
// Attempt to optimize the script before returning it.
1966+
zend_optimize_script(&persistent_script->script, ZCG(accel_directives).optimization_level, ZCG(accel_directives).opt_debug_level);
1967+
1968+
return zend_accel_load_script(persistent_script, /* from_memory */ 0);
1969+
}
1970+
return op_array;
1971+
}
1972+
19501973
int check_persistent_script_access(zend_persistent_script *persistent_script)
19511974
{
19521975
char *phar_path, *ptr;
@@ -1983,11 +2006,19 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type)
19832006
ZCG(cache_opline) = NULL;
19842007
ZCG(cache_persistent_script) = NULL;
19852008
if (file_handle->filename
1986-
&& ZCG(accel_directives).file_cache
19872009
&& ZCG(enabled) && accel_startup_ok) {
1988-
return file_cache_compile_file(file_handle, type);
2010+
if (no_cache) {
2011+
return no_cache_compile_file(file_handle, type);
2012+
} else if (ZCG(accel_directives).file_cache) {
2013+
return file_cache_compile_file(file_handle, type);
2014+
}
19892015
}
19902016
return accelerator_orig_compile_file(file_handle, type);
2017+
} else if (no_cache) {
2018+
// TODO: Why is opcache_get_status using accelerator_enabled instead of enabled?
2019+
ZCG(cache_opline) = NULL;
2020+
ZCG(cache_persistent_script) = NULL;
2021+
return no_cache_compile_file(file_handle, type);
19912022
} else if (file_cache_only) {
19922023
ZCG(cache_opline) = NULL;
19932024
ZCG(cache_persistent_script) = NULL;
@@ -2313,7 +2344,7 @@ static int persistent_stream_open_function(const char *filename, zend_file_handl
23132344
/* zend_resolve_path() replacement for PHP 5.3 and above */
23142345
static zend_string* persistent_zend_resolve_path(const char *filename, size_t filename_len)
23152346
{
2316-
if (!file_cache_only &&
2347+
if (!file_cache_only && !no_cache &&
23172348
ZCG(accelerator_enabled)) {
23182349

23192350
/* check if callback is called from include_once or it's a main request */
@@ -2437,7 +2468,7 @@ int accel_activate(INIT_FUNC_ARGS)
24372468
ZCG(cwd_key_len) = 0;
24382469
ZCG(cwd_check) = 1;
24392470

2440-
if (file_cache_only) {
2471+
if (file_cache_only || no_cache) {
24412472
ZCG(accelerator_enabled) = 0;
24422473
return SUCCESS;
24432474
}
@@ -2997,7 +3028,8 @@ static int accel_post_startup(void)
29973028
/* End of non-SHM dependent initializations */
29983029
/********************************************/
29993030
file_cache_only = ZCG(accel_directives).file_cache_only;
3000-
if (!file_cache_only) {
3031+
no_cache = ZCG(accel_directives).no_cache;
3032+
if (!file_cache_only && !no_cache) {
30013033
size_t shm_size = ZCG(accel_directives).memory_consumption;
30023034
#ifdef HAVE_JIT
30033035
size_t jit_size = 0;
@@ -3085,7 +3117,7 @@ static int accel_post_startup(void)
30853117
zend_shared_alloc_unlock();
30863118

30873119
SHM_PROTECT();
3088-
} else if (!ZCG(accel_directives).file_cache) {
3120+
} else if (file_cache_only && !ZCG(accel_directives).file_cache) {
30893121
accel_startup_ok = 0;
30903122
zend_accel_error(ACCEL_LOG_FATAL, "opcache.file_cache_only is set without a proper setting of opcache.file_cache");
30913123
return SUCCESS;
@@ -3149,7 +3181,7 @@ static int accel_post_startup(void)
31493181

31503182
zend_optimizer_startup();
31513183

3152-
if (!file_cache_only && ZCG(accel_directives).interned_strings_buffer) {
3184+
if (!file_cache_only && !no_cache && ZCG(accel_directives).interned_strings_buffer) {
31533185
accel_use_shm_interned_strings();
31543186
}
31553187

@@ -3167,6 +3199,7 @@ void accel_shutdown(void)
31673199
{
31683200
zend_ini_entry *ini_entry;
31693201
zend_bool _file_cache_only = 0;
3202+
zend_bool _no_cache = 0;
31703203

31713204
#ifdef HAVE_JIT
31723205
zend_jit_shutdown();
@@ -3188,15 +3221,16 @@ void accel_shutdown(void)
31883221
}
31893222

31903223
_file_cache_only = file_cache_only;
3224+
_no_cache = no_cache;
31913225

31923226
accel_reset_pcre_cache();
31933227

31943228
#ifdef ZTS
31953229
ts_free_id(accel_globals_id);
31963230
#endif
31973231

3198-
if (!_file_cache_only) {
3199-
/* Delay SHM detach */
3232+
if (!_file_cache_only && !_no_cache) {
3233+
/* Delay SHM detach - this only needs to be done if SHM is used */
32003234
orig_post_shutdown_cb = zend_post_shutdown_cb;
32013235
zend_post_shutdown_cb = accel_post_shutdown;
32023236
}
@@ -4786,6 +4820,10 @@ static int accel_finish_startup(void)
47864820
}
47874821

47884822
if (ZCG(accel_directives).preload && *ZCG(accel_directives).preload) {
4823+
if (no_cache) {
4824+
zend_accel_error(ACCEL_LOG_ERROR, "Preloading cannot be combined with no_cache");
4825+
return FAILURE;
4826+
}
47894827
#ifdef ZEND_WIN32
47904828
zend_accel_error(ACCEL_LOG_ERROR, "Preloading is not supported on Windows");
47914829
return FAILURE;
@@ -4809,6 +4847,10 @@ static int accel_finish_startup(void)
48094847
zend_bool old_reset_signals = SIGG(reset);
48104848
#endif
48114849

4850+
if (UNEXPECTED(no_cache)) {
4851+
zend_accel_error(ACCEL_LOG_WARNING, "Preloading doesn't work in \"no_cache\" mode");
4852+
return SUCCESS;
4853+
}
48124854
if (UNEXPECTED(file_cache_only)) {
48134855
zend_accel_error(ACCEL_LOG_WARNING, "Preloading doesn't work in \"file_cache_only\" mode");
48144856
return SUCCESS;

ext/opcache/ZendAccelerator.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ typedef struct _zend_accel_directives {
187187
#endif
188188
char *file_cache;
189189
zend_bool file_cache_only;
190+
zend_bool no_cache;
190191
zend_bool file_cache_consistency_checks;
191192
#if ENABLE_FILE_CACHE_FALLBACK
192193
zend_bool file_cache_fallback;
@@ -295,6 +296,7 @@ extern char accel_uname_id[32];
295296
#endif
296297
extern zend_bool accel_startup_ok;
297298
extern zend_bool file_cache_only;
299+
extern zend_bool no_cache;
298300
#if ENABLE_FILE_CACHE_FALLBACK
299301
extern zend_bool fallback_process;
300302
#endif

ext/opcache/tests/001_cli.phpt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
opcache.enable=1
55
opcache.enable_cli=1
66
opcache.file_cache_only=0
7+
opcache.optimization_level=-1
78
--SKIPIF--
89
<?php require_once('skipif.inc'); ?>
910
--FILE--
@@ -13,8 +14,10 @@ $status = opcache_get_status();
1314
var_dump($config["directives"]["opcache.enable"]);
1415
var_dump($config["directives"]["opcache.enable_cli"]);
1516
var_dump($status["opcache_enabled"]);
17+
var_dump($status["optimizations_enabled"]);
1618
?>
1719
--EXPECT--
1820
bool(true)
1921
bool(true)
2022
bool(true)
23+
bool(true)

ext/opcache/tests/no_cache_1.phpt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
no_cache_1: Opcache indicates if optimizations and caching are enabled
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.no_cache=1
7+
opcache.preload=
8+
--SKIPIF--
9+
<?php require_once('skipif.inc'); ?>
10+
--FILE--
11+
<?php
12+
$status = opcache_get_status();
13+
var_dump($status);
14+
?>
15+
--EXPECT--
16+
array(3) {
17+
["optimizations_enabled"]=>
18+
bool(true)
19+
["opcache_enabled"]=>
20+
bool(false)
21+
["no_cache"]=>
22+
bool(true)
23+
}

ext/opcache/tests/no_cache_2.phpt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--TEST--
2+
no_cache_2: no_cache takes precedence over file_cache
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.file_cache_only=1
7+
opcache.file_cache="{TMP}"
8+
opcache.no_cache=1
9+
opcache.opt_debug_level=0x20000
10+
opcache.optimization_level=-1
11+
opcache.preload=
12+
--SKIPIF--
13+
<?php require_once('skipif.inc'); ?>
14+
<?php if (PHP_OS_FAMILY == 'Windows') die('skip.. not for Windows'); ?>
15+
--FILE--
16+
<?php
17+
// Opcache should actually run.
18+
// Because no_cache is used, this will consistently emit debug output as a side effect.
19+
// It should also indicate that file_cache is not used.
20+
$status = opcache_get_status();
21+
var_dump($status);
22+
?>
23+
--EXPECTF--
24+
$_main:
25+
; (lines=7, args=0, vars=1, tmps=1)
26+
; (after optimizer)
27+
; %sno_cache_2.php:1-8
28+
0000 INIT_FCALL 0 %d string("opcache_get_status")
29+
0001 V1 = DO_ICALL
30+
0002 ASSIGN CV0($status) V1
31+
0003 INIT_FCALL 1 %d string("var_dump")
32+
0004 SEND_VAR CV0($status) 1
33+
0005 DO_ICALL
34+
0006 RETURN int(1)
35+
array(3) {
36+
["optimizations_enabled"]=>
37+
bool(true)
38+
["opcache_enabled"]=>
39+
bool(false)
40+
["no_cache"]=>
41+
bool(true)
42+
}

ext/opcache/zend_accelerator_module.c

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ ZEND_INI_BEGIN()
301301
#if ENABLE_FILE_CACHE_FALLBACK
302302
STD_PHP_INI_BOOLEAN("opcache.file_cache_fallback" , "1" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.file_cache_fallback, zend_accel_globals, accel_globals)
303303
#endif
304+
STD_PHP_INI_ENTRY("opcache.no_cache" , "0" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.no_cache, zend_accel_globals, accel_globals)
304305
#ifdef HAVE_HUGE_CODE_PAGES
305306
STD_PHP_INI_BOOLEAN("opcache.huge_code_pages" , "0" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.huge_code_pages, zend_accel_globals, accel_globals)
306307
#endif
@@ -405,6 +406,10 @@ void zend_accel_override_file_functions(void)
405406
{
406407
zend_function *old_function;
407408
if (ZCG(enabled) && accel_startup_ok && ZCG(accel_directives).file_override_enabled) {
409+
if (no_cache) {
410+
zend_accel_error(ACCEL_LOG_WARNING, "file_override_enabled has no effect when no_cache is set");
411+
return;
412+
}
408413
if (file_cache_only) {
409414
zend_accel_error(ACCEL_LOG_WARNING, "file_override_enabled has no effect when file_cache_only is set");
410415
return;
@@ -438,7 +443,7 @@ void zend_accel_info(ZEND_MODULE_INFO_FUNC_ARGS)
438443
{
439444
php_info_print_table_start();
440445

441-
if (ZCG(accelerator_enabled) || file_cache_only) {
446+
if ((ZCG(accelerator_enabled) || file_cache_only) && !no_cache) {
442447
php_info_print_table_row(2, "Opcode Caching", "Up and Running");
443448
} else {
444449
php_info_print_table_row(2, "Opcode Caching", "Disabled");
@@ -448,12 +453,12 @@ void zend_accel_info(ZEND_MODULE_INFO_FUNC_ARGS)
448453
} else {
449454
php_info_print_table_row(2, "Optimization", "Disabled");
450455
}
451-
if (!file_cache_only) {
456+
if (!file_cache_only && !no_cache) {
452457
php_info_print_table_row(2, "SHM Cache", "Enabled");
453458
} else {
454459
php_info_print_table_row(2, "SHM Cache", "Disabled");
455460
}
456-
if (ZCG(accel_directives).file_cache) {
461+
if (ZCG(accel_directives).file_cache && !no_cache) {
457462
php_info_print_table_row(2, "File Cache", "Enabled");
458463
} else {
459464
php_info_print_table_row(2, "File Cache", "Disabled");
@@ -471,7 +476,7 @@ void zend_accel_info(ZEND_MODULE_INFO_FUNC_ARGS)
471476
#else
472477
php_info_print_table_row(2, "JIT", "Not Available");
473478
#endif
474-
if (file_cache_only) {
479+
if (file_cache_only || no_cache) { // TODO test failure mode
475480
if (!accel_startup_ok || zps_api_failure_reason) {
476481
php_info_print_table_row(2, "Startup Failed", zps_api_failure_reason);
477482
} else {
@@ -613,8 +618,15 @@ ZEND_FUNCTION(opcache_get_status)
613618
array_init(return_value);
614619

615620
/* Trivia */
621+
/* Whether opcodes get optimized */
622+
add_assoc_bool(return_value, "optimizations_enabled", ZCG(enabled) && accel_startup_ok && ZCG(accel_directives).optimization_level);
623+
/* Whether the caching is enabled */
616624
add_assoc_bool(return_value, "opcache_enabled", ZCG(accelerator_enabled));
617625

626+
if (no_cache) {
627+
add_assoc_bool(return_value, "no_cache", 1);
628+
return;
629+
}
618630
if (ZCG(accel_directives).file_cache) {
619631
add_assoc_string(return_value, "file_cache", ZCG(accel_directives).file_cache);
620632
}
@@ -784,6 +796,7 @@ ZEND_FUNCTION(opcache_get_configuration)
784796
#if ENABLE_FILE_CACHE_FALLBACK
785797
add_assoc_bool(&directives, "opcache.file_cache_fallback", ZCG(accel_directives).file_cache_fallback);
786798
#endif
799+
add_assoc_bool(&directives, "opcache.no_cache", ZCG(accel_directives).no_cache);
787800

788801
add_assoc_long(&directives, "opcache.file_update_protection", ZCG(accel_directives).file_update_protection);
789802
add_assoc_long(&directives, "opcache.opt_debug_level", ZCG(accel_directives).opt_debug_level);

0 commit comments

Comments
 (0)