Существует мнение, что преинкремент потребляет меньше памяти, т.к. он инкрементирует саму переменную, а постинкремент помимо этого, копирует ее предыдущее значение во временную переменную.
Давайте рассмотрим следующий код:
1. <?php
2. $a = 1;
3. echo $a++;
4. $b = 1;
5. echo ++$b;
Этот код компилируется в следующие опкоды:
Finding entry points
Branch analysis from position: 0
Jump found. Position 1 = -2
filename: /in/tD1hS
function name:
number of ops: 7
compiled vars: !0 = $a, !1 = $b
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
2 0 E > ASSIGN !96, 1
3 1 POST_INC ~3 !96
2 ECHO ~3
4 3 ASSIGN !112, 1
5 4 PRE_INC $5 !112
5 ECHO $5
6 > RETURN 1
Как видно, операции отличаются только опкодом: POST_INC
vs PRE_INC
.
Рассмотрим, чем отличаются обработчики этих опкодов.
Ссылки на исходный код PHP 5.6.18:
--- pre inc
+++ post inc
Diff двух функций опкодов:
--- preinc
+++ postinc
@@ -1,46 +1,42 @@
-ZEND_VM_HANDLER(34, ZEND_PRE_INC, VAR|CV, ANY)
+ZEND_VM_HANDLER(36, ZEND_POST_INC, VAR|CV, ANY)
{
USE_OPLINE
zend_free_op free_op1;
- zval **var_ptr;
+ zval **var_ptr, *retval;
SAVE_OPLINE();
var_ptr = GET_OP1_ZVAL_PTR_PTR(BP_VAR_RW);
if (OP1_TYPE == IS_VAR && UNEXPECTED(var_ptr == NULL)) {
zend_error_noreturn(E_ERROR, "Cannot increment/decrement overloaded objects nor string offsets");
}
if (OP1_TYPE == IS_VAR && UNEXPECTED(*var_ptr == &EG(error_zval))) {
- if (RETURN_VALUE_USED(opline)) {
- PZVAL_LOCK(&EG(uninitialized_zval));
- EX_T(opline->result.var).var.ptr = &EG(uninitialized_zval);
- }
+ ZVAL_NULL(&EX_T(opline->result.var).tmp_var);
FREE_OP1_VAR_PTR();
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
+ retval = &EX_T(opline->result.var).tmp_var;
+ ZVAL_COPY_VALUE(retval, *var_ptr);
+ zendi_zval_copy_ctor(*retval);
+
SEPARATE_ZVAL_IF_NOT_REF(var_ptr);
if (UNEXPECTED(Z_TYPE_PP(var_ptr) == IS_OBJECT)
&& Z_OBJ_HANDLER_PP(var_ptr, get)
&& Z_OBJ_HANDLER_PP(var_ptr, set)) {
/* proxy object */
zval *val = Z_OBJ_HANDLER_PP(var_ptr, get)(*var_ptr TSRMLS_CC);
Z_ADDREF_P(val);
fast_increment_function(val);
Z_OBJ_HANDLER_PP(var_ptr, set)(var_ptr, val TSRMLS_CC);
zval_ptr_dtor(&val);
} else {
fast_increment_function(*var_ptr);
}
- if (RETURN_VALUE_USED(opline)) {
- PZVAL_LOCK(*var_ptr);
- EX_T(opline->result.var).var.ptr = *var_ptr;
- }
-
FREE_OP1_VAR_PTR();
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
Если опустим проверки, то основное отличие в двух блоках кода:
/* preinc */
// вначале происходит инкремент переменной в fast_increment_function(*var_ptr);
// И, если результат преинкремента используется,
if (RETURN_VALUE_USED(opline)) {
...
// Копируется указатель на переменную в результат
EX_T(opline->result.var).var.ptr = *var_ptr;
}
/* postinc */
// Получение адреса для результата постинкремента
retval = &EX_T(opline->result.var).tmp_var;
// Копирование значения из переменной в retval
ZVAL_COPY_VALUE(retval, *var_ptr);
...
// затем происходит инкремент переменной в fast_increment_function(*var_ptr);
На первый взгляд в постинкременте действительно происходит копирование переменной, когда в преинкременте копируется указатель на переменную.
Однако, EX_T
возвращает указатель на объединение temp_variable
. И в первом и во втором случае. Просто в преинкременте она используется как var.ptr
, а в постинкременте — tmp_var
. Но памяти в обоих случаях используется одинаковое количество.
#define EX_T(offset) (*EX_TMP_VAR(execute_data, offset))
#define EX_TMP_VAR(ex, n) ((temp_variable*)(((char*)(ex)) + ((int)(n))))
typedef union _temp_variable {
zval tmp_var;
struct {
zval **ptr_ptr;
zval *ptr;
zend_bool fcall_returned_reference;
} var;
struct {
zval **ptr_ptr; /* shared with var.ptr_ptr */
zval *str;
zend_uint offset;
} str_offset;
struct {
zval **ptr_ptr; /* shared with var.ptr_ptr */
zval *ptr; /* shared with var.ptr */
HashPointer fe_pos;
} fe;
zend_class_entry *class_entry;
} temp_variable;
Поэтому, постинкремент действительно потребляет больше памяти чем преинкремент, но не потому что происходит копирование переменной, а потому что в функции обработки опкода постинкремента присутствует дополнительный указатель zval *retval;
Еще немного контекстных вырезок исходного кода, если понадобится:
struct _zend_execute_data {
struct _zend_op *opline;
zend_function_state function_state;
zend_op_array *op_array;
zval *object;
HashTable *symbol_table;
struct _zend_execute_data *prev_execute_data;
zval *old_error_reporting;
zend_bool nested;
zval **original_return_value;
zend_class_entry *current_scope;
zend_class_entry *current_called_scope;
zval *current_this;
struct _zend_op *fast_ret; /* used by FAST_CALL/FAST_RET (finally keyword) */
zval *delayed_exception;
call_slot *call_slots;
call_slot *call;
};
#define USE_OPLINE zend_op *opline = EX(opline);
#define EX(element) execute_data->element
struct _zend_op {
opcode_handler_t handler;
znode_op op1;
znode_op op2;
znode_op result;
ulong extended_value;
uint lineno;
zend_uchar opcode;
zend_uchar op1_type;
zend_uchar op2_type;
zend_uchar result_type;
};
typedef union _znode_op {
zend_uint constant;
zend_uint var;
zend_uint num;
zend_ulong hash;
zend_uint opline_num; /* Needs to be signed */
zend_op *jmp_addr;
zval *zv;
zend_literal *literal;
void *ptr; /* Used for passing pointers from the compile to execution phase, currently used for traits */
} znode_op;
#define ZVAL_COPY_VALUE(z, v) \
do { \
(z)->value = (v)->value; \
Z_TYPE_P(z) = Z_TYPE_P(v); \
} while (0)
Written with StackEdit.