Skip to content

Instantly share code, notes, and snippets.

@m8rge m8rge/post-or-pre.md
Last active Feb 4, 2016

Embed
What would you like to do?
Что потребляет больше памяти в php: преинкремент или постинкремент?

Что потребляет больше памяти в php: преинкремент или постинкремент?

Существует мнение, что преинкремент потребляет меньше памяти, т.к. он инкрементирует саму переменную, а постинкремент помимо этого, копирует ее предыдущее значение во временную переменную.

Давайте рассмотрим следующий код:

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.