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實現,所以和固定數組比,相同數量的元素賦值,時間就變慢了。總之,普通數組和固定數組各有優劣,不能說固定數組時間和空間消耗低就是後,具體的情況需要考慮到具體的業務場景。