
PHP is a scripting language that compiles the files you need to run by default. During compilation, it extracts
opcodes , executes them, and then immediately destroys them. PHP was designed this way: when it goes to execute the R request, it forgets everything that was performed during the R-1 request.
It is very unlikely that on production servers the PHP code will change between the execution of several requests. So we can assume that during compilations the same source code is always read, and therefore the opcode will be exactly the same. And if you extract it for each script, it turns out a waste of time and resources.

')
Due to the long compilation time extensions have been developed for opcode caching. Their main task is to compile each PHP script
once and cache the resulting opcodes into shared memory so that they can be read and executed by each PHP workflow from your production pool (PHP-FPM is usually used).
As a result, the overall performance of the language greatly increases, and the launch of the script takes at least half the time (highly dependent on the script itself). Usually even less, because PHP doesn't need to compile the same scripts over and over again.
The more complex the application, the higher the efficiency of this optimization. If a program runs a bunch of files, for example, a framework-based application, or products like Wordpress, then the duration of running scripts can be reduced by 10-15 times. The fact is that the PHP compiler is slow because it has to convert one syntax to another, it tries to understand what you have written and somehow optimize the resulting code in order to speed up its execution. So yes, the compiler is slow and consumes a lot of memory. With the help of profilers like
Blackfire, we can predict the duration of the compilation.

Introduction to OPCache
The OPCache source code was opened in 2013, and it began to come with PHP 5.5.0. Since then, this is the standard solution for opcode caching in PHP. Here we will not consider other solutions, because of them I am familiar only with APC, whose support was discontinued in favor of OPCache. In short: if you used APC before, now use OPCache.
Now it is a PHP solution officially recommended by PHP developers for opcode caching. Of course, if you want, you can use other tools, but never activate more than one extension to cache opcodes at the same time . This will surely bring down PHP.
Also keep in mind that further development of OPCache will be carried out only within PHP 7, but not PHP 5. In this article we will look at OPCache for both versions, so you will see the difference (it is not too large).
So, OPCache is an extension, more precisely, a zend extension, embedded in PHP source code from version 5.5.0. It needs to be activated using the usual activation process via php.ini. As for distributions, check with the manual to make PHP and OPCache friendly.
Two functions of one product
OPCache has two main functions:
- Opcode caching.
- Optimization of opcodes.
Since OPCache runs the compiler to get and cache the codes, it can use this step to optimize them. In fact, we are talking about a variety of compiler optimizations. OPCache works as a multi-pass compiler optimizer.

Interiors OPCache
Let's see how OPCache works inside. If you want to check the code, you can take it, for example,
from here .
The idea of ​​caching opcodes will not be difficult to understand and analyze. You will need a good understanding of the work and architecture of the Zend engine, and you will immediately begin to notice the places where you can optimize.
Shared memory models
As you know, there are many shared memory models in different operating systems. Modern Unix systems use several approaches to shared memory usage by processes, the most popular of which are:
- System-V shm API
- POSIX API
- mmap API
- Unix socket API
OPCache can use the first three, if supported by your OS. INI-setting
opcache.preferred_memory_model explicitly set the desired model. If you leave the parameter at zero, OPCache will select the first model running on your platform, sequentially going through the table:
static const zend_shared_memory_handler_entry handler_table[] = {
The default is to use
mmap . This is a good model, developed and stable. Although it is less informative for system administrators than the System-V SHM model, like its
ipcs
and
ipcrm
.
As soon as OPCache starts (that is, PHP starts), it checks the shared memory model and allocates one large segment, which it will then distribute into parts. In this case, the segment will no longer be released or resized.
That is, when running PHP, OPCache allocates one large memory segment that is not freed or fragmented.
Segment size can be set in megabytes using the
opcache.memory_consumption INI settings. Do not save, ask more.
Never allow the exhaustion of shared memory , if this happens, the processes will be blocked. We will discuss this below.
Set the size of the segment according to your needs, and do not forget that a production server allocated for PHP processes can consume several dozen gigabytes of memory for PHP alone. So it is often allocated a segment of 1 GB or more, it all depends on the specific needs. If you are using a modern stack of applications, based on the framework, with a large number of dependencies, etc ... you can’t do at least a gigabyte.
The segment will be used by OPCache for several tasks:
- Caching the data structure of the script, including opcode caching.
- Creating a common internal (interned) string buffer.
- Storing hash tables of cached scripts.
- The state storage of global shared memory OPCache.
Remember that the shared memory segment contains not only opcodes, but also other things necessary for OPCache to work. So estimate, how much memory is needed, and set the desired size of the segment.

Opcode caching
Consider the details of the caching mechanism.
The idea is to copy into a shared memory (shm, shared memory) data of each pointer that does not change from request to request, that is, immutable data. A lot of them. After loading a previously used script from the shared memory, the pointer data is restored to the standard process memory associated with the current request. A working PHP compiler uses Zend Memory Manager (Zend Memory Manager, ZMM) to place each pointer. This type of memory is tied to the request, so ZMM will attempt to automatically free pointers when the current request is completed. In addition, these pointers are placed from the heap of the current request, so it turns out something like a private extended memory, which can not be used in conjunction with other PHP-processes. Therefore, the OPCache task is to view each structure returned by the PHP compiler, so as not to leave the pointer allocated to this pool, but to copy it into the dedicated pool of shared memory. And here we are talking about the compile time. Everything that has been posted by the compiler is considered immutable. The variable data will be created
by the Zend virtual machine during execution, so you can safely save everything created by the Zend compiler into shared memory. For example, functions and classes, pointers to function names, pointers to OPArray functions, class constants, names of declared class variables and, finally, their default content ... Many things are created in memory by a PHP compiler.
This model is used to reliably prevent locks. Later we will touch the topic of blocking. In fact, OPCache performs all of its work immediately, before execution, so already during the execution of the OPCache script there is nothing to do. The variable data will be created in the classic process heap using ZMM, and the immutable data will be restored from shared memory.
So, OPCache connects to the compiler and replaces the structure that the latter must fill during the compilation of scripts with its own. Then, instead of directly populating the tables of the Zend engine and internal structures, it forces the compiler to fill the
persistent_script
structure.
Here she is:
typedef struct _zend_persistent_script { ulong hash_value; char *full_path; unsigned int full_path_len; zend_op_array main_op_array; HashTable function_table; HashTable class_table; long compiler_halt_offset; int ping_auto_globals_mask; accel_time_t timestamp; zend_bool corrupted;
And so OPCache replaces the compiler structure with its
persistent_script
, by simply switching function pointers:
new_persistent_script = create_persistent_script(); orig_active_op_array = CG(active_op_array); orig_function_table = CG(function_table); orig_class_table = CG(class_table); orig_user_error_handler = EG(user_error_handler); CG(function_table) = &ZCG(function_table); EG(class_table) = CG(class_table) = &new_persistent_script->class_table; EG(user_error_handler) = NULL; zend_try { orig_compiler_options = CG(compiler_options); CG(compiler_options) |= ZEND_COMPILE_HANDLE_OP_ARRAY; CG(compiler_options) |= ZEND_COMPILE_IGNORE_INTERNAL_CLASSES; CG(compiler_options) |= ZEND_COMPILE_DELAYED_BINDING; CG(compiler_options) |= ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION; op_array = *op_array_p = accelerator_orig_compile_file(file_handle, type TSRMLS_CC); CG(compiler_options) = orig_compiler_options; } zend_catch { op_array = NULL; do_bailout = 1; CG(compiler_options) = orig_compiler_options; } zend_end_try(); CG(active_op_array) = orig_active_op_array; CG(function_table) = orig_function_table; EG(class_table) = CG(class_table) = orig_class_table; EG(user_error_handler) = orig_user_error_handler;
As you can see, the PHP compiler is completely isolated and disconnected from normally populated tables. Now it fills
persistent_script
structures. Next, OPCache should look at these structures and replace the pointers to the request with pointers to the shared memory. OPCache needed:
- Script Functions
- Script classes
- The main OPArray script.
- Script path
- The structure of the script itself.

Also, the compiler is passed some options that disable the optimizations it performs, for example,
ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION
and
ZEND_COMPILE_DELAYED_BINDING
. This adds OPCache work. Remember that OPCache connects to the Zend engine, it is not a patch for the source code.
Since we now have a
persitent_script
structure, we need to cache its information. The PHP compiler filled out our structures, but with the help of ZMM allocated memory from the edge: it will be released upon completion of the current request. Then we need to look at this memory and copy the contents into a segment of shared memory so that the collected information can be used for several requests and not re-calculated each time.
The process is structured as follows:
- The PHP script is placed in the cache and the total data size of each variable (all target pointer objects) is calculated.
- Already allocated shared memory is reserved for one large block of the same size.
- All the variable structures of the script are scanned, and the variable data of all pointer targets is copied into the newly reserved shared memory block.
- To download the script (when it comes to this) the opposite is done.
So, OPCache intelligently uses shared memory, never fragmenting it through releases and seals. For each script, it calculates the exact size of the total memory required to store information, and then copies the data there.
The memory is never released or returned to the OPCache. Therefore, it is used extremely efficiently and is not fragmented. This greatly improves the performance of shared memory, because there is no linked-list or B-tree (BTree) that you have to store and view when managing memory, which can be freed (as malloc / free does). OPCache stores data in a shared memory segment, and when they lose relevance (due to script validity checking), the buffers are not released, but are marked as “wasted”. When the lost memory reaches its maximum, OPCache restarts. This model is very different, for example, from APC. Its great advantage is that over time, performance does not fall, because the buffer from shared memory is never managed (not released, not compacted, etc.). All these memory management operations are a purely technical thing, not improving functionality, but reducing performance.
OPCache was designed to provide the highest possible performance with respect to the execution of the PHP environment. The “inviolability” of the shared memory segment also provides a very good processor cache access rate (especially L1 and L2), because OPCache also aligns the memory pointers with L1 / L2.
Caching a script primarily involves calculating the exact size of its data. Here is the calculation algorithm:
uint zend_accel_script_persist_calc(zend_persistent_script *new_persistent_script, char *key, unsigned int key_length TSRMLS_DC) { START_SIZE(); ADD_SIZE(zend_hash_persist_calc(&new_persistent_script->function_table, (int (*)(void* TSRMLS_DC)) zend_persist_op_array_calc, sizeof(zend_op_array) TSRMLS_CC)); ADD_SIZE(zend_accel_persist_class_table_calc(&new_persistent_script->class_table TSRMLS_CC)); ADD_SIZE(zend_persist_op_array_calc(&new_persistent_script->main_op_array TSRMLS_CC)); ADD_DUP_SIZE(key, key_length + 1); ADD_DUP_SIZE(new_persistent_script->full_path, new_persistent_script->full_path_len + 1); ADD_DUP_SIZE(new_persistent_script, sizeof(zend_persistent_script)); RETURN_SIZE(); }
I repeat: we need to cache:
- Script Functions
- Script classes
- The main OPArray script.
- Script path
- The structure of the script itself.
The iterative algorithm performs a deep search for functions, classes and OPArray: it caches the data of all pointers. For example, in PHP 5 for functions you need to copy to shared memory (shm):
- Hash Tables of Functions
- Container table of hash table functions (Bucket **)
- Container hash table of functions (Bucket *)
- Container key hash table of functions (char *)
- Index of data containers hash table of functions (void *)
- Data containers hash table functions (*)
- OPArray functions
- OPArray File Name (char *)
- OPArray Literals (names (char) and values ​​(zval) )
- Opcodes OPArray (zend_op *)
- OPArray function names function name (char *)
- arg_infos OPArray (zend_arg_info, as well as the name and the name of the class are both char )
- Array break-continue OPArray (zend_brk_cont_element *)
- OPArray static variables (Full hash table and zval *)
- OPArray documentation comments (char *)
- Try-catch OPArray array try- (zend_try_catch_element *)
- Compiled OPArray Variables (zend_compiled_variable *)
In PHP 7, the list is somewhat different due to the difference in structures (for example, a hash table). As I said, the idea is to copy the data of all the pointers into common memory. Since deep copying can affect intersecting structures, OPCache uses a translate table for storing pointers: each time a pointer is copied from conventional memory associated with a request to a common one, the table between the old and new pointer addresses is written to the table. The process responsible for copying first looks in the translation table to see if this data has already been copied. If copied, it uses old pointer data to avoid duplication:
void *_zend_shared_memdup(void *source, size_t size, zend_bool free_source TSRMLS_DC) { void **old_p, *retval; if (zend_hash_index_find(&xlat_table, (ulong)source, (void **)&old_p) == SUCCESS) { return *old_p; } retval = ZCG(mem);; ZCG(mem) = (void*)(((char*)ZCG(mem)) + ZEND_ALIGNED_SIZE(size)); memcpy(retval, source, size); if (free_source) { interned_efree((char*)source); } zend_shared_alloc_register_xlat_entry(source, retval); return retval; }
ZCG(mem)
is a fixed-size shared memory segment that is filled as items are added. Since it is already allocated, there is no need to allocate memory for each copy (this would reduce overall performance), just when filling a segment, the boundary of the pointers' addresses shifts.
We looked at a script caching algorithm that takes a pointer and data from a heap bound to a request and then copies them into shared memory, if this has not been done before. The loading algorithm does the exact opposite: it takes
persistent_script
from the shared memory and scans all its dynamic structures, copying the common pointers into pointers located in the memory bound to the process. After that, the script is ready to run using the Zend engine (Zend Engine Executor), now it does not embed the addresses of common pointers (which will lead to serious bugs when one script changes the structure of another). Now Zend is deceived by OPCache: he did not notice the pointer substitution that occurred before the execution of the script.
The process of copying from conventional memory to shared (caching the script) and back (loading the script) is well optimized, and even if you have to do a lot of copying or searching through a hash that does not improve performance, it still turns out much faster than running the PHP compiler every time .
Sharing internal row storage
The internal storage of strings (interned strings) is a good memory optimization, which appeared in PHP 5.4. It looks logical: when PHP encounters a string (char *), it stores it in a special buffer and uses the pointer again each time it encounters the same string, you can learn more about them from
this article .
They work like this:

All pointers use the same string instance. But there is one problem: the buffer of this internal line is used separately for each process and is mainly controlled by the PHP compiler. This means that in a PHP-FPM pool, every PHP workflow will save its own copy of this buffer. Like that:

This leads to large memory losses, especially when you have a lot of workflows, and when you use very large string in the code (hint: the explanatory comments in PHP are strings).
OPCache shares this buffer between all workflows in the pool. Something like this:

OPCache uses a shared memory segment to store all of these shared buffers. Therefore, when assigning a segment size, your use of internal row storage should be taken into account. Using the
opcache.interned_strings_buffer INI settings,
you can customize the use of shared memory for storage. Let me remind you once again: make sure that you have enough memory allocated. If you do not have enough space for these strings (
opcache.interned_strings_buffer is too low), OPCache will not restart. After all, it still has enough free shared memory, only the row storage buffer is full, which does not block request processing. You simply cannot save and share strings, and strings that use the memory of the PHP workflow will not be available. It is better to avoid such situations in order not to reduce performance.
Check the logs: when you run out of memory for this, OPCache will warn you about this:
if (ZCSG(interned_strings_top) + ZEND_MM_ALIGNED_SIZE(sizeof(Bucket) + nKeyLength) >= ZCSG(interned_strings_end)) { zend_accel_error(ACCEL_LOG_WARNING, "Interned string buffer overflow"); return arKey; }
Such lines include almost all types of lines encountered by the PHP compiler during its operation: variable names, “php strings”, function names, class names ... Comments that are today called “annotations” are also strings, and most often huge size. They occupy most of the buffer, so don't forget about them.
Locking mechanism
Since we are talking about shared memory, we should also talk about the mechanisms for locking memory. The bottom line is this:
every PHP process that wants to write to shared memory will block all other processes that also want to write to it . So the main difficulties are connected with writing, and not with reading. You may have 150 PHP processes reading from shared memory, but only one can write to it at a time. A write operation does not block reading, but only other write operations.
So
in OPCache there should be no deadlocks until you want to sharply warm up your cache . If, after the deployment of the code, you do not regulate traffic to the server, the scripts will begin to be intensively compiled and cached. And since the operation of writing a cache to shared memory is performed under the condition of an exclusive lock, then all processes will start, because some lucky person started writing to memory and blocked all the others. And when he removes the lock, all other processes that were waiting for their turn will find that the file they just compiled is already stored in shared memory. And then they will begin to destroy the result of the compilation in order to load data from the shared memory. This is an unforgivable waste of resources.
zend_shared_alloc_lock(TSRMLS_C); bucket = zend_accel_hash_find_entry(&ZCSG(hash), new_persistent_script->full_path, new_persistent_script->full_path_len + 1); if (bucket) { zend_persistent_script *existing_persistent_script = (zend_persistent_script *)bucket->data; if (!existing_persistent_script->corrupted) { if (!ZCG(accel_directives).revalidate_path && (!ZCG(accel_directives).validate_timestamps || (new_persistent_script->timestamp == existing_persistent_script->timestamp))) { zend_accel_add_key(key, key_length, bucket TSRMLS_CC); } zend_shared_alloc_unlock(TSRMLS_C); return new_persistent_script; } }
You need to disconnect the server from external traffic, deploy a new code, and pull the heaviest URLs with curl so that the curl requests gradually fill up the shared memory. When you are done with most of your scripts, you can send traffic to the server, and then active reading from shared memory will begin, and this does not lead to blocking. Of course, there may remain small scripts that have not been compiled yet, but since there are not many of them, this will have little effect on blocking the recording.
Avoid while writing PHP files and then using them. The reason is the same: when you write a new file to the root folder of a production server, then use it, then there is a chance that thousands of workflows will try to compile and cache it into shared memory. And then there will be a lock. PHP- OPCache INI-
opcache.blacklist-filename ( (glob pattern)).
, Unix —
fcntl()
:
void zend_shared_alloc_lock(TSRMLS_D) { while (1) { if (fcntl(lock_file, F_SETLKW, &mem_write_lock) == -1) { if (errno == EINTR) { continue; } zend_accel_error(ACCEL_LOG_ERROR, "Cannot create lock - %s (%d)", strerror(errno), errno); } break; } ZCG(locked) = 1; zend_hash_init(&xlat_table, 100, NULL, NULL, 1); }
, : , , .
, : . .
OPCache
:
- PHP ( PHP-FPM) OPCache , .
- OPCache . .
- OPCache .
- :
- , .
- .
- - .
- OPCache.
, OPCache ( , INI-
opcache.revalidate_freq ) , . : , . PHP OPCache, PHP (
stat()
) : OPCache , «»
stat()
.
(timestamp)
opcache.validate_timestamps opcache.revalidate_freq , , OPCache «wasted».
OPCache INI- opcache.max_wasted_percentage INI. . There are no other options.
memory_used = zend_accel_script_persist_calc(new_persistent_script, key, key_length TSRMLS_CC); ZCG(mem) = zend_shared_alloc(memory_used); if (!ZCG(mem)) { zend_accel_schedule_restart_if_necessary(ACCEL_RESTART_OOM TSRMLS_CC); zend_shared_alloc_unlock(TSRMLS_C); return new_persistent_script; }

, , . «», OPCache . .
, . OPCache , . , : , . - . , , . .
., production-, ( , : OPCache persistent-, ). :
- ( ).
- OPCache (
opcache_reset()
) FPM ( , — )). - .
- FPM, , curl- .
- .
shell- 50 . ,
lsof
kill
. Unix ;-)
GUI- OPCache.
opcache_get_status()
:

.
(cache keys) .
OPCache , -, . - OPCache . ? .
OPCache . ,
realpath_cache , . ,
opcache.revalidate_path 1 realpath cache ( , , ).
, OPCache ,
realpath . , INI-
opcache.revalidate_path 1. , OPCache
unresolved . , , , OPCache - unresolved , ( ).
opcache.use_cwd 1, OPCache
cwd
.
,
require_once "./foo.php";
. , PHP ( ),
opcache.use_cwd 1. , ,
opcache.revalidate_path .
realpath- . www- , OPCache ,
opcache_reset()
.
- realpath- .
opcache.use_cwd opcache.revalidate_path 1, . realpath OPCache, PHP ,
realpath_cache
.
, documentroot. , FPM- FastCGI, . , Lighttpd Nginx:
- ( ).
- FPM, PHP ( OPCache). , realpath-, . FPM. , , .
- .
- FPM-. curl- .
- .
, , :
- , PHP- FPM- production-.
- FPM-, . production-.
- FPM-: , .
- documentroot- , FPM-.
- , , , , . , documentroot- ( realpath-), . , production-. shell- 80.
, OPCache . : . OPCache , , : .
, .
OPCache
opcache_get_status()
— , GUI —
num_cached_keys . : INI-
opcache.max_accelerated_files . , OPCache . , . .
require_once
, OPCache . ,
include_once
, .
OPCache - persistent-, . - , . .
num_cached_scripts num_cached_keys , OPache.
num_cached_keys .
max_cached_keys , .
, , , OPache (INI-
opcache.log_verbosity_level ). , , , OOM- (OutOfMemory): -.

static void zend_accel_add_key(char *key, unsigned int key_length, zend_accel_hash_entry *bucket TSRMLS_DC) { if (!zend_accel_hash_find(&ZCSG(hash), key, key_length + 1)) { if (zend_accel_hash_is_full(&ZCSG(hash))) { zend_accel_error(ACCEL_LOG_DEBUG, "No more entries in hash table!"); ZSMMG(memory_exhausted) = 1; zend_accel_schedule_restart_if_necessary(ACCEL_RESTART_HASH TSRMLS_CC); } else { char *new_key = zend_shared_alloc(key_length + 1); if (new_key) { memcpy(new_key, key, key_length + 1); if (zend_accel_hash_update(&ZCSG(hash), new_key, key_length + 1, 1, bucket)) { zend_accel_error(ACCEL_LOG_INFO, "Added key '%s'", new_key); } } else { zend_accel_schedule_restart_if_necessary(ACCEL_RESTART_OOM TSRMLS_CC); } } } }
:

PHP OPCache,
opcache.memory_consumption (shm). (
opcache.interned_strings_buffer ). - persistent- .
opcache.max_accelerated_files .
OPCache, . OPCache ( ). «», OPCache, ().
It might look like this:

- persistent- , , OPCache ( ).
OPCache
, , Symfony, :
- production ( opcache.validate_timestamps 0)
- , runtime . Symfony.
- :
- opcache.memory_consumption . .
- opcache.interned_strings_buffer . . : OPCache , , «» PHP ( opcache.save_comments = 1 ), , , .
- opcache.max_accelerated_files . . , .
- opcache.opcache.revalidate_path opcache.use_cwd . .
- opcache.enable_file_override , .
- opcache.blacklist_filename , runtime. .
- opcache.consistency_checks , , , .
«».
then opcache.max_wasted_percentage . FPM. FPM, , .
.
OPCache
Introduction
, . OPCache . , ,
Zend . ,
, . «
». , .
, OPArray, , .
, , « PHP». ,
IS_VAR IS_CV ,
IS_CONST —
IS_TMP_VAR . , runtime, .
OPCache
IS_CONST . ( runtime); CGF- ( ) . . PHP: , . - , OPCache OPArray ( OPArray ), . PHP - — , : . , , , , . Java ++, «» - . PHP .
PHP . , , . - .
OPCache - . INI-
opcache.optimization_level . :
, PHP 5 , , , OPCache. PHP 7 .
Example:
if (false) { echo "foo"; } else { echo "bar"; }
:

:

,
if(false)
, Zend
ZEND_ECHO
. , . , runtime.
, , ( ).
IS_CONST IS_CV , :
if ($a) { echo "foo"; } else { echo "bar"; }
, PHP 5 PHP 7:
if (__DIR__ == '/tmp') { echo "foo"; } else { echo "bar"; }
PHP 7
__DIR__
, OPCache. OPCache. PHP 5.6
__DIR__
, . OPCache.
. PHP 5.6 PHP 7 OPCache, . , PHP 5.6 , PHP 7, PHP 5.6 , PHP 7 ( OPCache).
-
OPCache
IS_TMP_VAR IS_CONST . , . - , . :
function_exists()
is_callable()
, .extension_loaded()
, dl()
.defined()
constant()
, .dirname()
.strlen()
dirname()
- ( PHP 7).
Take a look at an example:
if (function_exists('array_merge')) { echo 'yes'; }
, runtime:

:

, . , :
if function_exists('my_custom_function')) { }
, '__'. , PHP OPCache . :
function my_custom_function() { } if function_exists('my_custom_function')) { }
, ,
( ).
dirname()
( PHP 7):
if (dirname(__FILE__) == '/tmp') { echo 'yo'; }
:

:

strlen()
PHP 7 . , . For example:
if (strlen(dirname(__FILE__)) == 4) { echo "yes"; } else { echo "no"; }
:

:

, /, OPCache «» (, «» ).
(Transtyping)
OPCache
IS_CONST , , . runtime:
$a = 8; $c = $a + "42"; echo $c;
:

:

true-
ZEND_ADD
: .
ADD
. , runtime , , , . .
OPCache, :
if (ZEND_OPTIMIZER_PASS_2 & OPTIMIZATION_LEVEL) { zend_op *opline; zend_op *end = op_array->opcodes + op_array->last; opline = op_array->opcodes; while (opline < end) { switch (opline->opcode) { case ZEND_ADD: case ZEND_SUB: case ZEND_MUL: case ZEND_DIV: if (ZEND_OP1_TYPE(opline) == IS_CONST) { if (ZEND_OP1_LITERAL(opline).type == IS_STRING) { convert_scalar_to_number(&ZEND_OP1_LITERAL(opline) TSRMLS_CC); } } case ZEND_ASSIGN_ADD: case ZEND_ASSIGN_SUB: case ZEND_ASSIGN_MUL: case ZEND_ASSIGN_DIV: if (opline->extended_value != 0) { break; } if (ZEND_OP2_TYPE(opline) == IS_CONST) { if (ZEND_OP2_LITERAL(opline).type == IS_STRING) { convert_scalar_to_number(&ZEND_OP2_LITERAL(opline) TSRMLS_CC); } } break;
, PHP 7. , PHP 7 OPCache ( ), , PHP 5.
IS_CONST , . PHP 5 , OPCache:
$a = 4 + "33"; echo $a;
:

:

4 + 33
ZEND_ADD
, . runtime, . : PHP 7 , PHP 5 OPCache.
. , .
$i = "foo"; $i = $i + 42; echo $i;
:

:

Zend VM
ZEND_ASSIGN_ADD
ZEND_ADD
ZEND_ASSIGN
.
$i+=3;
.
ZEND_ASSIGN_ADD
, ( , )
:
$j = 4; $j++; echo $j;
:

:

OPCache
++$i
$i++
, .
ZEND_POST_INC
— , , , ,
ZEND_PRE_INC
: , ( ). ,
ZEND_POST_INC
, ,
ZEND_FREE
. OPCache
ZEND_PRE_INC
ZEND_FREE
: runtime.
PHP-? , . , , . Let's look at an example:
const FOO = "bar"; echo FOO;

:

. , , , runtime .
define()
const
, :
define('FOO', 'bar'); echo FOO;
:

:

define()
, , runtime, (
define()
). This is very bad.
const
DECLARE_CONST
.
Zend .
(Multiple jump target resolution)
, . ( ). , , . — , . PHP- .
if
,
switch
,
while
,
try
,
foreach
,
?
: — . true, , — .
, . landing- , landing-. , «».
For example:
if ($a) { goto a; } else { echo "no"; } a: echo "a";
:

( ): « $a 0, 3, «no». 4, «a»».
- « 3, 4». « 4»? :

« $a , 2 «a», «no»». , ? , . ,
while
if
,
goto
,
switch
,
try-catches
, .. OPArray . , . ( ) . runtime.
Conclusion
. , « » (early returns). try-catch switch-break. PHP, .
, , . OPCache , , PHP , , … , , . .
OPCache , PHP. PHP 7, . PHP 7 ( ) PHP 5 ( PHP 5 ).
, , . , . , , , . , , OPCache. : , . ( runtime).
the end
, OPCache - . , , . OPCache PHP, . PHP- , , . , — PHP-FPM SAPI.