Section 6 Copy On Write
Before we begin, we can look at a simple piece of code:
<? $foo = 1; $bar = $foo;echo$foo + $bar; ? >Copy the code
Executing this code prints out the number 2. To analyze this code from a memory perspective, it “might” do something like this: Allocate a block of memory to the foo variable with a 1 in it; Allocate a block of memory to the bar variable, also store a 1, and finally calculate the output. In fact, we found that the foo and bar variables, because they have the same value, can use the same block of memory, saving a 1 in memory usage and eliminating the computational overhead of allocating and managing memory addresses. Yes, many systems involved in memory management implement the same value shared memory strategy: copy-on-write
A lot of the time, we’re scared of the concepts because of the terminology, but the basic principles are very simple. This section describes the implementation of the copy-on-write strategy in PHP:
There are many application scenarios of Copy on Write (also abbreviated as COW), such as optimization of memory usage in process replication in Linux, and similar applications in various programming languages, such as STL of C++. COW is a common optimization method, which can be classified as: Resource deferred allocation. Resources are used only when they are really needed, and copy-on-write usually reduces resource usage.
Note: To save space, COW is used to represent “copy-on-write” in the following paragraphs.
Delay memory replication optimization
As mentioned earlier, COW in PHP can be simply described as: If you assign to a variable by way of assignment, you do not allocate new memory to store the value of the new variable. Instead, you simply share memory with a counter. Only when one of the references to the variable changes, you allocate new space to store the value content to reduce memory usage. PHP optimizes memory with COW in many scenarios. For example, multiple assignments of variables, passing function parameters, and modifying arguments inside a function.
Let’s look at an example of looking at memory to make it easier to see COW’s obvious role in memory usage optimization:
<?php //例二
$j = 1;
var_dump(memory_get_usage());
$tipi = array_fill(0, 100000, 'php-internal');
var_dump(memory_get_usage());
$tipi_copy = $tipi;
var_dump(memory_get_usage());
foreach($tipi_copy as $i){
$j += count($i);
}
var_dump(memory_get_usage()); //----- result ----- $PHP t. HP int(630904) int(10479840) int(10479944) int(10480040)Copy the code
The above code typically highlights COW. When the array variable $tipi is assigned to $tipi_copy, memory usage does not immediately increase by half, nor does it change significantly when the number $tipi_copy is iterated over. Here, the $tipi_copy and $tipi variables point to the same block of memory together, without copying.
That is, even if we don’t use references, when a variable is assigned a value, as long as we don’t change the value of the variable, no new memory is allocated to store the data. Therefore, it is easy to think of some scenarios where COW can be very effective in controlling memory usage: variables are only used for calculation with little modification, such as passing function parameters, copying large arrays, etc., without changing variable values.
Copy separates the value of the change
Multiple variables of the same value Shared the same piece of memory indeed save memory space, but is can change the value of a variable, if in the above example, point to the same memory value has changed (or could change), you need to change the value of the “separation”, the “separation”, is “copy”.
In PHP, the Zend engine uses ref_count and is_ref variables to distinguish whether the same zval address is shared by multiple variables:
Ref_count and is_ref are defined in the zval structure (see section 1 of Chapter 1). Ref_count is a reference count that identifies how many variables this zval is referenced by. This is an automatic reference to COW. If it is 0, it will be destroyed. For more on these two variables, skip to Chapter 3, Section 6: Implementation of Variable Assignment and Destruction. $a=$b; With $a = & $b; There is no difference in PHP’s use of memory (when the value does not change);
Example 2: What happens if the value of $copy changes? :
<? $tipi = array_fill(0, 3, 'php-internal'); $tipi = array_fill(0, 3, 'php-internal'); // Array_fill is no longer used here. Why? $tipi[0] = 'php-internal'; $tipi[1] = 'php-internal'; $tipi[2] = 'php-internal';var_dump(memory_get_usage());
$copy = $tipi;
xdebug_debug_zval('tipi', 'copy');
var_dump(memory_get_usage());
$copy[0] = 'php-internal';
xdebug_debug_zval('tipi', 'copy');
var_dump(memory_get_usage()); / / -- -- -- -- -- the execution result -- -- -- -- - $PHP t.p HP int (629384) tipi: (refcount = 2, is_ref = 0) =array (0 => (refcount=1, is_ref=0)='php-internal',
1 => (refcount=1, is_ref=0)='php-internal',
2 => (refcount=1, is_ref=0)='php-internal')
copy: (refcount=2, is_ref=0)=array (0 => (refcount=1, is_ref=0)='php-internal',
1 => (refcount=1, is_ref=0)='php-internal',
2 => (refcount=1, is_ref=0)='php-internal')
int(629512)
tipi: (refcount=1, is_ref=0)=array (0 => (refcount=1, is_ref=0)='php-internal',
1 => (refcount=2, is_ref=0)='php-internal',
2 => (refcount=2, is_ref=0)='php-internal')
copy: (refcount=1, is_ref=0)=array (0 => (refcount=1, is_ref=0)='php-internal',
1 => (refcount=2, is_ref=0)='php-internal',
2 => (refcount=2, is_ref=0)='php-internal')
int(630088)Copy the code
In this example, we can find the following characteristics:
- $copy = $tipi; This basic assignment will trigger COW’s memory “sharing” without memory replication.
- COW’s granularity is zVAL structure, and all variables in PHP are based on ZVAL, so COW’s scope is all variables. For the collection composed of ZVAL structures (such as groups of numbers and objects, etc.), complex objects are decomposed into the smallest granularity when memory needs to be copied. In this way, when a part of a complex object in memory is modified, all elements of the object do not have to be “separated and copied” into a memory copy.
Array_fill () implements PHP_FUNCTION(array_fill) in $PHP_SRC/ext/standard/array.c. Xdebug_debug_zval () is a function in the Xdebug extension that outputs references to variables within Zend. If you don’t have the XDebug extension installed, you can use debug_zval_dump() instead. Reference: www.php.net/manual/zh/f…
Implement copy-on-write
PHP implements COW based on reference-counting ref_count (ref) and is_ref (ref). If there is an extra variable pointer, add 1 to ref_count and subtract 1 from it if it is reduced to 0. Similarly, an additional mandatory reference & increases is_ref by 1 and subtracts it by 1.
Here’s a typical example:
<? $foo = 1; $foo = 1; xdebug_debug_zval('foo'); $bar = $foo; xdebug_debug_zval('foo'); $bar = 2; xdebug_debug_zval('foo'); ? > / / -- -- -- -- -- the execution result -- -- -- -- -- foo: (refcount = 1, is_ref = 0) = 1 foo: (refcount = 2, is_ref = 0) = 1 foo: (refcount = 1, is_ref = 0) = 1Copy the code
From the variables section, we know that when $foo is assigned, the value of the $foo variable is referred to only by the $foo variable. When $foo is assigned to $bar, PHP does not make a copy of memory to $bar. Instead, $foo and $bar point to the same address. The reference count is also increased by 1, which is the new 2. Then we change the value of $bar, and if the memory that the $bar variable points to is directly needed, the value of $foo will change as well. That’s not what we want. So, the PHP kernel makes a copy of the memory and updates it to the assigned: 2 (also known as the variable separation operation), and the memory that the original $foo variable points to is only $foo, so the reference count is updated to refCount =1.
It looks simple enough, but the situation is much more complicated because of the & operator. See the following example:
This example illustrates PHP’s problematic handling of the & operator: when $beauty=&$pan; When, both variables essentially become reference types, causing the seemingly ordinary variable $pan to behave the same as &$pan in some internal processing. In particular, using reference variables in array elements can cause problems. (See last example)
Most work in PHP is text processing, while variables are carriers. The use of different types of variables runs through the life cycle of PHP. The COW strategy for variables also reflects the Zend engine’s processing of variables and their memory.
Zend/zend_execute.c ======================================== zend_assign_to_variable_reference(); zend_assign_to_variable(); zend_assign_to_object(); zend_assign_to_variable(); / / and the following macro definition using the Zend/Zend. H = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = # define Z_REFCOUNT (z) Z_REFCOUNT_P (&) (z) # define Z_SET_REFCOUNT(z, rc) Z_SET_REFCOUNT_P(&(z), rc) #define Z_ADDREF(z) Z_ADDREF_P(&(z)) #define Z_DELREF(z) Z_DELREF_P(&(z)) #define Z_ISREF(z) Z_ISREF_P(&(z)) #define Z_SET_ISREF(z) Z_SET_ISREF_P(&(z)) #define Z_UNSET_ISREF(z) Z_UNSET_ISREF_P(&(z)) #define Z_SET_ISREF_TO(z, isref) Z_SET_ISREF_TO_P(&(z), isref)Copy the code
Finally, be careful with references&
References and variable reference counts mentioned above are not the same thing as references in PHP. References are similar to Pointers in C. They can access the same content through different identifiers, but references in PHP are simply variable aliases without the flexibility and limitations of C instructions.
There are a lot of surprising behaviors in PHP, some of which have been chosen not to be fixed for historical reasons that don’t break compatibility, or some of which are used in fewer scenarios. You can only avoid these pitfalls as much as possible in PHP. Take the following example.
Because reference operators lead to optimization of PHP’s COW strategy, the use of references also requires a clear understanding of the behavior of references so as not to misuse them and cause bugs that are difficult to understand. If you think you know enough about references in PHP, try explaining the following example:
<? php $foo['love'] = 1; $bar = &$foo['love']; $tipi = $foo; $tipi['love'] = '2';echo $foo['love'];Copy the code
$bar contaminating $foo[‘love’] into a reference, so Zend does not copy the changes to $tipi[‘love’].