Skip to content

Commit efeaf13

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 69d46c3 commit efeaf13

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
@@ -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,11 +1945,19 @@ 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 (no_cache) {
1950+
return no_cache_compile_file(file_handle, type);
1951+
} else if (ZCG(accel_directives).file_cache) {
1952+
return file_cache_compile_file(file_handle, type);
1953+
}
19281954
}
19291955
return accelerator_orig_compile_file(file_handle, type);
1956+
} else if (no_cache) {
1957+
// TODO: Why is opcache_get_status using accelerator_enabled instead of enabled?
1958+
ZCG(cache_opline) = NULL;
1959+
ZCG(cache_persistent_script) = NULL;
1960+
return no_cache_compile_file(file_handle, type);
19301961
} else if (file_cache_only) {
19311962
ZCG(cache_opline) = NULL;
19321963
ZCG(cache_persistent_script) = NULL;
@@ -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,7 +3055,7 @@ 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;
@@ -3083,7 +3115,7 @@ static int accel_post_startup(void)
30833115

30843116
zend_optimizer_startup();
30853117

3086-
if (!file_cache_only && ZCG(accel_directives).interned_strings_buffer) {
3118+
if (!file_cache_only && !no_cache && ZCG(accel_directives).interned_strings_buffer) {
30873119
accel_use_shm_interned_strings();
30883120
}
30893121

@@ -3101,6 +3133,7 @@ void accel_shutdown(void)
31013133
{
31023134
zend_ini_entry *ini_entry;
31033135
zend_bool _file_cache_only = 0;
3136+
zend_bool _no_cache = 0;
31043137

31053138
#ifdef HAVE_JIT
31063139
zend_jit_shutdown();
@@ -3122,15 +3155,16 @@ void accel_shutdown(void)
31223155
}
31233156

31243157
_file_cache_only = file_cache_only;
3158+
_no_cache = no_cache;
31253159

31263160
accel_reset_pcre_cache();
31273161

31283162
#ifdef ZTS
31293163
ts_free_id(accel_globals_id);
31303164
#endif
31313165

3132-
if (!_file_cache_only) {
3133-
/* Delay SHM detach */
3166+
if (!_file_cache_only && !_no_cache) {
3167+
/* Delay SHM detach - this only needs to be done if SHM is used */
31343168
orig_post_shutdown_cb = zend_post_shutdown_cb;
31353169
zend_post_shutdown_cb = accel_post_shutdown;
31363170
}
@@ -4720,6 +4754,10 @@ static int accel_finish_startup(void)
47204754
}
47214755

47224756
if (ZCG(accel_directives).preload && *ZCG(accel_directives).preload) {
4757+
if (no_cache) {
4758+
zend_accel_error(ACCEL_LOG_ERROR, "Preloading cannot be combined with no_cache");
4759+
return FAILURE;
4760+
}
47234761
#ifdef ZEND_WIN32
47244762
zend_accel_error(ACCEL_LOG_ERROR, "Preloading is not supported on Windows");
47254763
return FAILURE;
@@ -4743,6 +4781,10 @@ static int accel_finish_startup(void)
47434781
zend_bool old_reset_signals = SIGG(reset);
47444782
#endif
47454783

4784+
if (UNEXPECTED(no_cache)) {
4785+
zend_accel_error(ACCEL_LOG_WARNING, "Preloading doesn't work in \"no_cache\" mode");
4786+
return SUCCESS;
4787+
}
47464788
if (UNEXPECTED(file_cache_only)) {
47474789
zend_accel_error(ACCEL_LOG_WARNING, "Preloading doesn't work in \"file_cache_only\" mode");
47484790
return SUCCESS;

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/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
@@ -240,6 +240,7 @@ ZEND_INI_BEGIN()
240240
#if ENABLE_FILE_CACHE_FALLBACK
241241
STD_PHP_INI_BOOLEAN("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
@@ -333,6 +334,10 @@ void zend_accel_override_file_functions(void)
333334
{
334335
zend_function *old_function;
335336
if (ZCG(enabled) && accel_startup_ok && ZCG(accel_directives).file_override_enabled) {
337+
if (no_cache) {
338+
zend_accel_error(ACCEL_LOG_WARNING, "file_override_enabled has no effect when no_cache is set");
339+
return;
340+
}
336341
if (file_cache_only) {
337342
zend_accel_error(ACCEL_LOG_WARNING, "file_override_enabled has no effect when file_cache_only is set");
338343
return;
@@ -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,12 +381,12 @@ 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");
383388
}
384-
if (ZCG(accel_directives).file_cache) {
389+
if (ZCG(accel_directives).file_cache && !no_cache) {
385390
php_info_print_table_row(2, "File Cache", "Enabled");
386391
} else {
387392
php_info_print_table_row(2, "File 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 {
@@ -537,8 +542,15 @@ ZEND_FUNCTION(opcache_get_status)
537542
array_init(return_value);
538543

539544
/* Trivia */
545+
/* Whether opcodes get optimized */
546+
add_assoc_bool(return_value, "optimizations_enabled", ZCG(enabled) && accel_startup_ok && ZCG(accel_directives).optimization_level);
547+
/* Whether the caching is enabled */
540548
add_assoc_bool(return_value, "opcache_enabled", ZCG(accelerator_enabled));
541549

550+
if (no_cache) {
551+
add_assoc_bool(return_value, "no_cache", 1);
552+
return;
553+
}
542554
if (ZCG(accel_directives).file_cache) {
543555
add_assoc_string(return_value, "file_cache", ZCG(accel_directives).file_cache);
544556
}
@@ -707,6 +719,7 @@ ZEND_FUNCTION(opcache_get_configuration)
707719
#if ENABLE_FILE_CACHE_FALLBACK
708720
add_assoc_bool(&directives, "opcache.file_cache_fallback", ZCG(accel_directives).file_cache_fallback);
709721
#endif
722+
add_assoc_bool(&directives, "opcache.no_cache", ZCG(accel_directives).no_cache);
710723

711724
add_assoc_long(&directives, "opcache.file_update_protection", ZCG(accel_directives).file_update_protection);
712725
add_assoc_long(&directives, "opcache.opt_debug_level", ZCG(accel_directives).opt_debug_level);

0 commit comments

Comments
 (0)