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 内の関数テーブルのクエリが 1 つ必要になるためです。 。一般的な配列の場合、PHP は内部的にハッシュテーブルを介して配列の値に対応し、これは PHP ソース コード Zend/zend_hash で定義されます。 h ハッシュ関連の構造定義と関数。
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) + となります。 要素自体がスペースを占有します。 (スペースの計算では、添字が整数の場合のみが考慮されます)
次のコードに示すように、対応する固定配列は、ユーザーによって渡されたサイズによって直接初期化されます。同じ 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; } }
時間比較
固定配列の場合、メモリの適用は 1 ステップで実行され、メモリが不足している場合はエラーが報告されます。
通常の配列の場合、配列空間が動的に割り当てられるため、要素数が事前に分からないため、PHPが空の配列を初期化する際、デフォルトでは8スロットになりますが、スロットが足りない場合は*2となります。 hashTbale の要素数が nTableSize より大きい場合、hashTable のサイズが変更され、再ハッシュされるため、かなりの時間がかかります。
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 を使用してデータ要素を迅速に見つけることです。また、PHP の配列スペースは動的に割り当てられるため、内部実装では C 言語が使用されます。配列のスペース割り当てには固定割り当てしかありません。PHP 配列が動的にスペースを割り当てているとユーザーに感じさせるには、サイズ変更と再ハッシュによってのみ実現できます。そのため、固定配列と比較して、同じ数の要素を割り当てる時間は遅くなります。 。つまり、通常のアレイと固定アレイにはそれぞれ利点と欠点があり、特定のビジネス シナリオを考慮する必要があるため、固定アレイの方が時間とスペースの消費が少ないとは言えません。