Skip to content

Instantly share code, notes, and snippets.

@m8rge
Last active February 4, 2016 10:54
Show Gist options
  • Save m8rge/e64812ffc0cd2a23cf41 to your computer and use it in GitHub Desktop.
Save m8rge/e64812ffc0cd2a23cf41 to your computer and use it in GitHub Desktop.
Что потребляет больше памяти в 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