php的數組和spl固定數組

php固定數組隸屬於php標準庫(spl)的一種數據結構。和php普通數組相比,固定數組只能用整形定義其下標,並且如名字所示,是固定長度,它的優點是比普通數組佔用的內存少,而且更快速,具體原因下文會做分析,先做一個簡單的測試,將10W個a放入到數組中。
define("MAX", 100000);

//simple array
function simple_arr()
{
        $i = MAX;
	$arr = array();
	while ($i--)
		$arr[$i]= 'a';
}

// fix array
function fix_arr()
{	
	$i = MAX;
	$arr = new SplFixedArray(MAX);
	while ($i--)
		$arr[$i]= 'a';		
}

//fix array with set
function fix_set_arr()
{
	$i = MAX;
	$arr = new SplFixedArray(MAX);
	while ($i--)
		$arr->offsetSet($i, "a");
}

時間消耗

一般數組:0.084696054458618

固定數組:0.048405885696411

固定數組調用offsetSet方法複製:0.27650499343872

內存消耗

一般數組:9324672

固定數組:4800464

固定數組調用offsetSet方法複製:4800344


空間消耗對比

從空間和時間的效率來看,固定數組的消耗都比一般數組少了很可觀。固定數組通過擴展中的內置函數offsetSet賦值比通過下標賦值時間慢的多,這個因爲用擴展中的內置方法給數組賦值,php內部需要多一次函數表的查詢。

在空間方面,對一般數組,php內部是通過hashtable來存儲,hashtable中的每一個槽位對應數組中的一個值,在php源碼Zend/zend_hash.h中定義了hash相關的結構體定義和函數。

typedef struct bucket {
	ulong h;						/* Used for numeric indexing */
	uint nKeyLength;
	void *pData;
	void *pDataPtr;
	struct bucket *pListNext;
	struct bucket *pListLast;
	struct bucket *pNext;
	struct bucket *pLast;
	const char *arKey;
} Bucket;

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
	

如上面代碼所示,一個10個元素的php數組所站的空間是sizeof(HashTable) + 10 * size(Bucket) + 元素本身佔用空間,這是代碼層面的算術,其實在php內部會複雜一點,HashTable的nTableSize永遠是2^n,所以即使是10個元素,php內部通過 簡單算法能實現佔用2^4,也即16個槽位,所以實際佔用空間是sizeof(HashTable) + 16 * sizeof(Bucket)  + 元素本身佔用空間。(空間的計算只考慮下標是整數的情況下)

而對應固定數組直接通過用戶傳人的size大小初始化數組,如下面代碼所示:同樣10個元素的數組,所需要的空間只有10* 元素本身佔用空間。

static void spl_fixedarray_init(spl_fixedarray *array, long size TSRMLS_DC) /* {{{ */
{
	if (size > 0) {
		array->size = 0; /* reset size in case ecalloc() fails */
		array->elements = ecalloc(size, sizeof(zval *));
		array->size = size;
	} else {
		array->elements = NULL;
		array->size = 0;
	}
}

時間方面對比

對於固定數組來說,對內存的申請一步到位了,當內存不夠時候會報錯,當內存用不完時,也就浪費在那裏。

對於普通數組,因爲是動態分配數組空間,由於預先不知道要有多少元素,php初始化空數組的的時候,默認8個槽位,但槽位不夠的時候,會再分配*2的空間,當元素元素的數量大於hashTbale中的nTableSize的時候,會resize和rehash hashTable,在resize和rehash的過程中,時間的消耗相當可觀了。

static int zend_hash_do_resize(HashTable *ht)
{
	Bucket **t;
#ifdef ZEND_SIGNALS
	TSRMLS_FETCH();
#endif

	IS_CONSISTENT(ht);

	if ((ht->nTableSize << 1) > 0) {	/* Let's double the table size */
		t = (Bucket **) perealloc_recoverable(ht->arBuckets, (ht->nTableSize << 1) * sizeof(Bucket *), ht->persistent);
		if (t) {
			HANDLE_BLOCK_INTERRUPTIONS();
			ht->arBuckets = t;
			ht->nTableSize = (ht->nTableSize << 1);
			ht->nTableMask = ht->nTableSize - 1;
			zend_hash_rehash(ht);
			HANDLE_UNBLOCK_INTERRUPTIONS();
			return SUCCESS;
		}
		return FAILURE;
	}
	return SUCCESS;
}

ZEND_API int zend_hash_rehash(HashTable *ht)
{
	Bucket *p;
	uint nIndex;

	IS_CONSISTENT(ht);
	if (UNEXPECTED(ht->nNumOfElements == 0)) {
		return SUCCESS;
	}

	memset(ht->arBuckets, 0, ht->nTableSize * sizeof(Bucket *));
	p = ht->pListHead;
	while (p != NULL) {
		nIndex = p->h & ht->nTableMask;
		CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex]);
		ht->arBuckets[nIndex] = p;
		p = p->pListNext;
	}
	return SUCCESS;
}


通過對比發現php普通數組的內存和時間消耗和固定數組相比大部分都用在了符號表上。php內部實現的一個重要思想是通過空間來換取時間,用hashTable能夠快速定位到數據的元素,它甚至把hashTable設計成雙向鏈表,又因爲php數組空間是動態分配的,而內部是用c實現的,c語言對數組空間分配只有固定分配,爲了實現讓用戶感覺php數組是動態分配空間的,只能通過resize和rehash實現,所以和固定數組比,相同數量的元素賦值,時間就變慢了。總之,普通數組和固定數組各有優劣,不能說固定數組時間和空間消耗低就是後,具體的情況需要考慮到具體的業務場景。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章