PHP变量的CV类型
在我们使用VLD扩展查看PHP生成的中间代码时,经常会看到有这样一项:compiled vars: !0 = $a,或者如果使用更加详细的 -dvld.verbosity=3参数,会看到IS_CV,IS_VAR等类型。这里所说的Compiled vars和IS_CV都与今天我们所要了解的CV类型有莫大的关系。
CV者,Compiled variable也。
CV类型是PHP编译过程中产生的一种变量类型,以类似于缓存的方式,提高某些变量的存储速度。
与CV类型同一级别的类型还有:
#define IS_CONST (1<<0) #define IS_TMP_VAR (1<<1) #define IS_VAR (1<<2) #define IS_UNUSED (1<<3) /* Unused variable */ #define IS_CV (1<<4) /* Compiled variable */ |
比如最常见的赋值语句:$a = 10;
以php -dvld.active=1 -dvld.verbosity=3 test.php(这段代码在放在test.php文件中)查看。
function name: (null) number of ops: 3 compiled vars: !0 = $a line # * op return operands ---------------------------------------------- 2 0 > EXT_STMT RES[ IS_UNUSED ] OP1[ IS_UNUSED ] OP2[ IS_UNUSED ] 1 ASSIGN OP1[IS_CV !0 ] OP2[ , IS_CONST (0) 10 ] 13 2 > RETURN OP1[IS_CONST (0) 1 ] |
如上我们可以知道10的类型为IS_CONST,$a的类型为IS_CV。
这里的CV类型在代码运行时存储在哪呢?在什么时候能起到性能优化的作用?
我们知道PHP中间代码运行的数据大部分都放在全局变量execute_data中,对于这个变量我们通常以EX(element)的方式调用,如EX(CVs)、EX(opline)等等。通过对execute_data所有字段的查阅我们可以大概知道CV类型变量存放在EX(CVs)中。
如何判别呢?
同样,以上面的简单赋值语句为例,依据其中间代码(ZEND_ASSIGN),操作数的类型(IS_CV和IS_CONST),可以得出其中间代码最终执行的函数为:ZEND_ASSIGN_SPEC_CV_CONST_HANDLE。在此函数中,对于操作数据处理:
zval *value = &opline->op2.u.constant; zval **variable_ptr_ptr = _get_zval_ptr_ptr_cv(&opline->op1, EX(Ts), BP_VAR_W TSRMLS_CC); |
op2为右值,op1为左值,左值的查找处理是_get_zval_ptr_ptr_cv。如下:
static zend_always_inline zval **_get_zval_ptr_ptr_cv(const znode *node, const temp_variable *Ts, int type TSRMLS_DC) { zval ***ptr = &CV_OF(node->u.var); if (UNEXPECTED(*ptr == NULL)) { return _get_zval_cv_lookup(ptr, node->u.var, type TSRMLS_CC); } return *ptr; } // 函数中的CV_OF宏定义 #define CV_OF(i) (EG(current_execute_data)->CVs[i]) |
上面的函数和宏定义道出了CV这个类缓存机制的实现过程和CV类型的存储位置。
在函数中,程序会先判断变量是否存在于EX(CVs) – 这就是存储位置,如果存在则直接返回,否则调用_get_zval_cv_lookup,通过HashTable操作在EG(active_symbol_table)表中查找变量。虽然HashTable的查找操作已经比较快了,但是与原始的数组操作相比还是不在一个数量级。这就是CV类型变量的性能优化点所在。
以上是变量的赋值操作,也是变量的创建过程,与此对应,在变量销毁时PHP内核应该会对此种类型的变量执行清除HashTable和数组两个操作,以unset操作为例,针对IS_CV类型的变量,其中间代码最终会执行ZEND_UNSET_VAR_SPEC_CV_HANDLER。在此函数中不管是ZEND_QUICK_SET类型的操作,还是常规的unset操作,都会对HashTable(EG(active_symbol_table)或target_symbol_table)和数组(EX(CVs)和ex->CVs)执行清除操作。