PHP原始碼陣列統計count分析

raullf111111111發表於2017-02-14

偶然在百度知道中看到有個同學問起count及strlen的效率問題,好吧這個問題我當初沒理解透徹,認為其不屬兩個不一樣的東西不可比較,後來看了樓主的回覆才反應過來,所以自己也去找了下原始碼檢視下。現在總結下檢視到的結果並記錄之。

zend給PHP的所有變數都用結構的方式去儲存,而字串的儲存和陣列的儲存也是不同的,陣列採用的是hash表的方式去儲存(大家知道hash儲存的地址有效的減少衝突-hash雜湊表的概念你懂的),而在php中的結構體上表現如下: 
程式碼如下: 
//檔案1:zend/zend.h 
/* 
* zval 
*/ 
typedef struct _zval_struct zval; 
... 
typedef union _zvalue_value { 
long lval; /* long value */ 
double dval; /* double value */ 
struct { 
char *val; 
int len; 
} str; 
HashTable *ht; /* hash table value */ 
zend_object_value obj; 
} zvalue_value; 

struct _zval_struct { 
/* Variable information */ 
zvalue_value value; /* value */ 
zend_uint refcount__gc; 
zend_uchar type; /* active type */ 
zend_uchar is_ref__gc; 
}; 
//hash表的結構如下 
//檔案2:zend/zend_hash.h 
typedef struct _hashtable { 
uint nTableSize; 
uint nTableMask; 
uint nNumOfElements; 
ulong nNextFreeElement; 
Bucket *pInternalPointer; /* Used for element traversal */ 
Bucket *pListHead; 
Bucket *pListTail; 
Bucket **arBuckets; 
dtor_func_t pDestructor; 
zend_bool persistent; 
unsigned char nApplyCount; 
zend_bool bApplyProtection; 
#if ZEND_DEBUG 
int inconsistent; 
#endif 

HashTable; 

一般的變數(字串)在使用strlen獲取長度的時候,其實獲取的就是zvalue_value.str這個結構中的len屬性,效率上O(1)次,特別說明的一點是:strlen在php中並沒有核心的實現,而是在使用了zend中的巨集定義來獲取: 

程式碼如下: 
//檔案3:zend/zend_operators.php 
#define Z_STRLEN(zval) (zval).value.str.len 
... 
#define Z_STRLEN_P(zval_p) Z_STRLEN(*zval_p) 
... 
#define Z_STRLEN_PP(zval_pp) Z_STRLEN(**zval_pp) 

而對於陣列的count操作,其實有兩種結果,在count 的api中也提到了第二個引數mode《Http://www.php.net/manual/en/function.count.php》,這個mode引數指明瞭,是否需要重新統計,而它的重新統計將會遍歷一次陣列,效率上是O(N)[N:長度],預設情況下是不重新統計,那這個時候將會直接輸出hashtable中的nNumOfElements,此時的效率也是O(1)次:count程式碼如下: 
程式碼如下: 
//檔案4:ext/standard/array.c 
PHP_FUNCTION(count) 

zval *array; 
long mode = COUNT_NORMAL; 

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|l", &array, &mode) == FAILURE) { 
return; 


switch (Z_TYPE_P(array)) { 
case IS_NULL: 
RETURN_LONG(0); 
break; 
case IS_ARRAY: 
RETURN_LONG (php_count_recursive (array, mode TSRMLS_CC)); 
break; 
..... 

//php_count_recursive的實現 
static int php_count_recursive(zval *array, long mode TSRMLS_DC) /* {{{ */ 

long cnt = 0; 
zval **element; 

if (Z_TYPE_P(array) == IS_ARRAY) { 
//錯誤處理 
if (Z_ARRVAL_P(array)->nApplyCount > 1) { 
php_error_docref(NULL TSRMLS_CC, E_WARNING, "recursion detected"); 
return 0; 

//通過zend_hash_num_elements直接獲得長度 
cnt = zend_hash_num_elements(Z_ARRVAL_P(array)); 

//如果指定了需要重新統計,則會進入一次迴圈統計 
if (mode == COUNT_RECURSIVE) { 
HashPosition pos; 

for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(array), &pos); 
zend_hash_get_current_data_ex(Z_ARRVAL_P(array), (void **) &element, &pos) == SUCCESS; 
zend_hash_move_forward_ex(Z_ARRVAL_P(array), &pos) 
) { 
Z_ARRVAL_P(array)->nApplyCount++; 
cnt += php_count_recursive(*element, COUNT_RECURSIVE TSRMLS_CC); 
Z_ARRVAL_P(array)->nApplyCount--; 




return cnt; 


//檔案5:zend/zend_hash.c 
//zend_hash_num_elements的實現 
ZEND_API int zend_hash_num_elements(const HashTable *ht) 

IS_CONSISTENT(ht); 

return ht->nNumOfElements; 

原文連結:http://www.kubiji.cn/topic-id4090.html

相關文章