PHP 7の配列ハッシュテヌブル

ハッシュテヌブルは、あらゆる深刻なCプログラムで、あらゆる堎所で䜿甚されたす。 基本的に、プログラマは文字列でむンデックスを䜜成しお倀を「配列」に栌玍できたすが、Cでは敎数配列キヌのみが蚱可されたす。 ハッシュテヌブルでは、小文字のキヌが最初にハッシュされ、次にテヌブルのサむズに瞮小されたす。 ここでは衝突が発生する可胜性があるため、衝突を解決するアルゎリズムが必芁です。 同様のアルゎリズムがいく぀かあり、PHPはリンクリスト戊略を䜿甚したす。

Web䞊には、ハッシュテヌブルの構造ずその実装に぀いお詳しく説明しおいる玠晎らしい蚘事がたくさんありたす。 http://preshing.com/から始めるこずができたす。 ただし、無数のハッシュテヌブル構造のオプションがあり、それらのいずれも完党ではなく、プロセッササむクル、メモリ䜿甚量、たたはスレッド環境の適切なスケヌリングの最適化にもかかわらず、それぞれにトレヌドオフがありたす。 デヌタを远加する堎合はより良いオプション、怜玢する堎合は他のオプションなどがありたす。より重芁なものに応じお実装を遞択したす。

PHP 5のハッシュテヌブルに぀いおは、PHP 7のハッシュテヌブルに関する優れた蚘事の著者であるNikicず䞀緒に曞いたphpinternalsbookで詳しく説明されおいたす。 おそらくあなたもそれを面癜いず思うでしょう。 確かに、それはリリヌスの前に曞かれたので、その䞭のいく぀かのものはわずかに異なりたす。

ここでは、PHP 7でハッシュテヌブルがどのように配眮されるか、C蚀語の芳点からハッシュテヌブルを操䜜する方法、およびPHPツヌルを䜿甚しお配列ず呌ばれる構造を䜿甚しおハッシュテヌブルを管理する方法を詳しく芋おいきたす。 ゜ヌスコヌドのほずんどはzend_hash.cで入手できたす。 ハッシュテヌブルはどこでも通垞は蟞曞ずしお䜿甚するこずを忘れないでください。したがっお、プロセッサですばやく凊理され、メモリをほずんど消費しないように蚭蚈する必芁がありたす。 ハッシュテヌブルが䜿甚される堎所はロヌカル配列だけではないため、これらの構造はPHPの党䜓的なパフォヌマンスに決定的な圱響を及がしたす。

ハッシュテヌブルの蚭蚈


以䞋で詳现に怜蚎する倚くの芏定がありたす。


HashTable構造に぀いお考えおみたしょう。

 struct _zend_array { zend_refcounted_h gc; union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar flags, zend_uchar nApplyCount, zend_uchar nIteratorsCount, zend_uchar reserve) } v; uint32_t flags; /*  32  */ } u; uint32_t nTableMask; /*  — nTableSize */ Bucket *arData; /*    */ uint32_t nNumUsed; /*     arData */ uint32_t nNumOfElements; /*      arData */ uint32_t nTableSize; /*  ,      */ uint32_t nInternalPointer; /*    */ zend_long nNextFreeElement; /*     */ dtor_func_t pDestructor; /*   */ }; 

䞀郚のフィヌルドはめったに䜿甚されないため、それらに぀いおは説明したせん。

この構造のサむズは56バむトです LP64モデルによる。

最も興味深いデヌタフィヌルドは、 Bucketチェヌンのメモリ領域ぞのポむンタの䞀皮であるarDataです。 Bucket自䜓は配列内の単䞀のセルです。

 typedef struct _Bucket { zval val; /*  */ zend_ulong h; /*  (  ) */ zend_string *key; /*    NULL    */ } Bucket; 

お気づきかもしれたせんが、 zvalはBucket構造に保存されたす。 これはzvalぞのポむンタではなく、構造自䜓であるこずに泚意しおください。 これは、PHP 7ではzvalがヒヌプに配眮されなくなったためですPHP 5ずは異なりたすが、PHP 7では、 zvalポむンタヌずしお保存されたタヌゲット倀PHP文字列などを配眮できたす。

メモリ内の配眮がどのように発生するかを芋おみたしょう。



ご芧のずおり、ハッシュテヌブルに配眮されたデヌタは、隣接するメモリセクションarData保存されたす。

順序を維持しながらアむテムを远加する


PHPは、芁玠が配列に远加されるずきに芁玠の順序を維持する必芁がありたす。 foreachを配列に適甚するず、キヌに関係なく、配列に配眮された正確な順序でデヌタを受け取りたす。

 $a = [9=>"foo", 2 => 42, []]; var_dump($a); array(3) { [9]=> string(3) "foo" [2]=> int(42) [10]=> array(0) { } } 

これは、ハッシュテヌブルの実装に倚くの制限を課した重芁なポむントです。 すべおのデヌタは、互いに隣り合うメモリに配眮されたす。 zvalでは、 arData C-arrayのフィヌルドにあるBucketパックされお栌玍されたす。 このようなもの

 $a = [3=> 'foo', 8 => 'bar', 'baz' => []]; 



このアプロヌチのおかげで、ハッシュテヌブルを簡単に反埩できたすarData配列をarDataだけです。 本質的に、これはメモリの迅速な順次スキャンであり、プロセッサキャッシュリ゜ヌスをほずんど消費したせん。 arDataからのすべおのデヌタは1行に配眮でき、各セルぞのアクセスには玄1ナノ秒かかりたす。 泚プロセッサキャッシュの䜿甚効率を高めるために、 arData 64ビットモデルに埓っおアラむメントされたす64ビットフル呜什のアラむメントの最適化も䜿甚されたす。 ハッシュテヌブルの反埩コヌドは次のようになりたす。

 size_t i; Bucket p; zval val; for (i=0; i < ht->nTableSize; i++) { p = ht->arData[i]; val = p.val; /*  -   val */ } 

デヌタは゜ヌトされ、次のarDataセルに枡されたす。 この手順を完了するには、 nNumUsedフィヌルドに栌玍されおいる、この配列で次に䜿甚可胜なセルを芚えおおいおnNumUsed 。 新しい倀が远加されるたびに、 ht->nNumUsed++を远加しht->nNumUsed++ 。 nNumUsedの芁玠数がハッシュテヌブルの芁玠数 nNumOfElements に達するず、コンパクトアルゎリズムたたはサむズ倉曎アルゎリズムを実行したす。これに぀いおは以䞋で説明したす。

文字列キヌを䜿甚しおハッシュテヌブルにアむテムを远加する簡略化されたビュヌ

 idx = ht->nNumUsed++; /*      */ ht->nNumOfElements++; /*    */ /* ... */ p = ht->arData + idx; /*     bucket  arData */ p->key = key; /*  ,     */ /* ... */ p->h = h = ZSTR_H(key); /*   bucket    */ ZVAL_COPY_VALUE(&p->val, pData); /*     bucket':   */ 

倀を消去


倀を削陀しおも、 arData配列arData枛少したり再配列arDataたりarDataん。 そうしないず、メモリ内のデヌタを移動する必芁があるため、パフォヌマンスが劇的に䜎䞋したす。 そのため、ハッシュテヌブルからデヌタを削陀する堎合、 arDataの察応するセルは、 zval UNDEFずいう特別な方法で単玔にマヌクされたす。



したがっお、このような「空の」セルを凊理できるように、反埩コヌドを少し再構築する必芁がありたす。

 size_t i; Bucket p; zval val; for (i=0; i < ht->nTableSize; i++) { p = ht->arData[i]; val = p.val; if (Z_TYPE(val) == IS_UNDEF) { continue; } /*  -   val */ } 

以䞋では、ハッシュテヌブルのサむズが倉曎されたずきに䜕が起こるか、および「空の」セルが消えるようにarData配列を再線成する必芁がありたす圧瞮。

キヌハッシュ


キヌはハッシュおよび圧瞮され、圧瞮されたハッシュ倀から倉換されおarDataむンデックス付けされるarDataたす。 キヌは敎数であっおも圧瞮されたす。 これは、配列の境界に収たるようにするために必芁です。

arData盎接あるように、圧瞮された倀にむンデックスをarDataこずはできないこずにarDataしおarData 。 結局、これは配列のむンデックスに䜿甚されるキヌがハッシュから取埗したキヌに盎接察応するこずを意味したす。これは、PHPのハッシュテヌブルのプロパティの1぀、芁玠の順序の保持に違反したす。

たずえば、最初にfooキヌを远加し、次にbarキヌを远加するず、最初のキヌはハッシュされおキヌ5に圧瞮され、2番目のキヌはキヌ3に圧瞮されたすarData[5]デヌタがarData[5] arData[3] 、デヌタバヌがデヌタfooに移動するこずがわかりたす。 たた、 arData反埩凊理する堎合arData芁玠は远加された順序ではなく転送されたす。



したがっお、割り圓おられたarData境界に収たるように、キヌをハッシュしおから圧瞮したす。 ただし、PHP 5ずは異なり、そのたた䜿甚したせん。最初に倉換テヌブルを䜿甚しおキヌを倉換する必芁がありたす。 ハッシュ/圧瞮によっお取埗された1぀の敎数倀ず、 arData配列内のアドレス指定に䜿甚される別の敎数倀を単玔に比范したす。

泚意点が1぀ありたす。倉換テヌブルのメモリは、 arDataベクトルの背埌に慎重に配眮されたす。 これarDataテヌブルはすでにarData隣にarDataれおおり、同じアドレス空間に残っおいるため、テヌブルが別のメモリ領域を䜿甚するarDataたす。 これにより、デヌタの局所性が向䞊したす。 これは、8芁玠のハッシュテヌブル可胜な最小サむズの堎合に説明されたスキヌムがどのように芋えるかです



これで、fooキヌはDJB33Xでハッシュされ、必芁なサむズ nTableMask にモゞュロ圧瞮されたす。 結果の倀は、盎接セルではなく arData 倉換 arDataにアクセスするために䜿甚できるむンデックスです。

これらのセルは、 arData開始䜍眮からの負のシフトによっおアクセスされたす。 2぀のメモリ領域が結合されたため、メモリにデヌタを順番に保存できたす。 nTableMaskはテヌブルのサむズの負の倀に察応するため、モゞュヌルずしお取埗するず0〜–7の倀を取埗したす。 これでメモリにアクセスできたす。 arDataバッファヌ党䜓を配眮する堎合、次の匏を䜿甚しおサむズを蚈算したす。

* bucket' + * (uint32) .

以䞋に、バッファが2぀の郚分にどのように分割されおいるかを明確に瀺したす。

 #define HT_HASH_SIZE(nTableMask) (((size_t)(uint32_t)-(int32_t)(nTableMask)) * sizeof(uint32_t)) #define HT_DATA_SIZE(nTableSize) ((size_t)(nTableSize) * sizeof(Bucket)) #define HT_SIZE_EX(nTableSize, nTableMask) (HT_DATA_SIZE((nTableSize)) + HT_HASH_SIZE((nTableMask))) #define HT_SIZE(ht) HT_SIZE_EX((ht)->nTableSize, (ht)->nTableMask) Bucket *arData; arData = emalloc(HT_SIZE(ht)); /*      */ 

マクロが実行されるず、次の結果が埗られたす。

 (((size_t)(((ht)->nTableSize)) * sizeof(Bucket)) + (((size_t)(uint32_t)-(int32_t)(((ht)->nTableMask))) * sizeof(uint32_t))) 

いいね

衝突解決


それでは、衝突がどのように解決されるかを芋おみたしょう。 芚えおいるように、ハッシュテヌブルでは、ハッシュおよび圧瞮されたずきのいく぀かのキヌが同じ倉換むンデックスに察応するこずがありたす。 そのため、倉換むンデックスを受け取ったら、それを䜿甚しおarDataからデヌタを抜出し、ハッシュずキヌを比范しお、これが必芁かどうかを確認したす。 デヌタが正しくない堎合、 zval.u2.nextフィヌルドを䜿甚しおリンクリストをzval.u2.nextたす。このフィヌルドには、デヌタを入力するための次のセルが反映されたす。

リンクリストは、埓来のリンクリストのようにメモリスキャッタリングされないこずに泚意しおください。 ヒヌプから受け取ったいく぀かのメモリに配眮されたポむンタをarDataではarDataおそらくアドレス空間に散らばっおいる、メモリから完党なベクトルarDataを読み取りたす。 そしお、これは、PHP 7および蚀語党䜓のハッシュテヌブルのパフォヌマンスを向䞊させる䞻な理由の1぀です。

PHP 7では、ハッシュテヌブルのデヌタの局所性は非垞に高くなっおいたす。 デヌタは通垞、第1レベルのプロセッサキャッシュにあるため、ほずんどの堎合、アクセスは1ナノ秒で行われたす。

ハッシュに芁玠を远加し、衝突を解決する方法を芋おみたしょう。

 idx = ht->nNumUsed++; /*      */ ht->nNumOfElements++; /*    */ /* ... */ p = ht->arData + idx; /*   arData bucket    */ p->key = key; /*     */ /* ... */ p->h = h = ZSTR_H(key); /*   bucket    */ ZVAL_COPY_VALUE(&p->val, pData); /*     bucket':   */ nIndex = h | ht->nTableMask; /*     */ Z_NEXT(p->val) = HT_HASH(ht, nIndex); /*        */ HT_HASH(ht, nIndex) = HT_IDX_TO_HASH(idx); /*       */ 

削陀ず同じこず

 h = zend_string_hash_val(key); /*      */ nIndex = h | ht->nTableMask; /*     */ idx = HT_HASH(ht, nIndex); /*  ,    */ while (idx != HT_INVALID_IDX) { /*     */ p = HT_HASH_TO_BUCKET(ht, idx); /*  bucket    */ if ((p->key == key) || /*  ?    ? */ (p->h == h && /* ...    */ p->key && /*   (   ) */ ZSTR_LEN(p->key) == ZSTR_LEN(key) && /*      */ memcmp(ZSTR_VAL(p->key), ZSTR_VAL(key), ZSTR_LEN(key)) == 0)) { /*     ? */ _zend_hash_del_el_ex(ht, idx, p, prev); /*   !   */ return SUCCESS; } prev = p; idx = Z_NEXT(p->val); /*     */ } return FAILURE; 

倉換セルずハッシュの初期化


HT_INVALID_IDXは、倉換テヌブルに入れる特別なフラグです。 これは、「この倉換はどこにも通じないため、続行する必芁がない」こずを意味したす。

2段階の初期化には特定の利点があり、䜜成されたばかりの空のハッシュテヌブルPHPの䞀般的なケヌスの圱響を最小限に抑えるこずができたす。 テヌブルを䜜成するずきに、 arDataバケットセルず、 HT_INVALID_IDXフラグを配眮する2぀の倉換セルを同時に䜜成したす。 次に、最初の倉換セル HT_INVALID_IDX 、ここにはデヌタはありたせんを指すマスクを適甚したす。

 #define HT_MIN_MASK ((uint32_t) -2) #define HT_HASH_SIZE(nTableMask) (((size_t)(uint32_t)-(int32_t)(nTableMask)) * sizeof(uint32_t)) #define HT_SET_DATA_ADDR(ht, ptr) do { (ht)->arData = (Bucket*)(((char*)(ptr)) + HT_HASH_SIZE((ht)->nTableMask)); } while (0) static const uint32_t uninitialized_bucket[-HT_MIN_MASK] = {HT_INVALID_IDX, HT_INVALID_IDX}; /* hash lazy init */ ZEND_API void ZEND_FASTCALL _zend_hash_init(HashTable *ht, uint32_t nSize, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC) { /* ... */ ht->nTableSize = zend_hash_check_size(nSize); ht->nTableMask = HT_MIN_MASK; HT_SET_DATA_ADDR(ht, &uninitialized_bucket); ht->nNumUsed = 0; ht->nNumOfElements = 0; } 

ここでは、束を䜿甚できないこずに泚意しおください。 静的constメモリゟヌンで十分であり、はるかに簡単です uninitialized_bucket 。

最初の芁玠を远加した埌、ハッシュテヌブルを完党に初期化したす。 ぀たり、芁求されたサむズに応じお、最埌に必芁な倉換セルを䜜成したすデフォルトでは8セルから始たりたす。 メモリ内の配眮はヒヌプから来たす。

 (ht)->nTableMask = -(ht)->nTableSize; HT_SET_DATA_ADDR(ht, pemalloc(HT_SIZE(ht), (ht)->u.flags & HASH_FLAG_PERSISTENT)); memset(&HT_HASH(ht, (ht)->nTableMask), HT_INVALID_IDX, HT_HASH_SIZE((ht)->nTableMask)) 

HT_HASHマクロを䜿甚HT_HASHず、負のオフセットが䜿甚されるメモリ内にあるバッファヌのその郚分の倉換セルにアクセスできHT_HASH 。 倉換テヌブルのセルにはarDataバッファヌの先頭からマむナスのむンデックスが付けられるため、テヌブルマスクは垞に負になりたす。 ここでは、Cでのプログラミングがすべおの栄光で明らかになりたした。数十億個のセルが利甚可胜で、無限に泳ぎ、justれないでください。

レむゞヌ初期化レむゞヌ初期化ハッシュテヌブルの䟋䜜成されたしたが、これたでハッシュは配眮されおいたせん。



ハッシュの断片化、拡倧、および圧瞮


ハッシュテヌブルがいっぱいで、新しい芁玠を远加する必芁がある堎合は、サむズを倧きくする必芁がありたす。 これは、Cの埓来のハヌド制限配列ず比范したハッシュテヌブルの倧きな利点です。増加するたびに、ハッシュテヌブルのサむズは2倍になりたす。 テヌブルのサむズが倧きくなるず、メモリにC配列arBucketを事前に割り圓お、空のセルに特別なUNDEF倀を配眮するこずを思い出しおください。 その結果、私たちは激しく蚘憶を倱いたす。 損倱は​​次の匏で蚈算されたす。

( – ) * Bucket

このメモリはすべおUNDEFセルで構成され、そこにデヌタが配眮されるのを埅ちたす。

たずえば、ハッシュテヌブルに1024個のセルがあり、新しい芁玠を远加したす。 テヌブルは2048個のセルに拡倧し、そのうちの1023個は空です。 1023 * 32バむト=箄32 Kb。 これは、PHPでハッシュテヌブルを実装するこずの欠点の1぀です。

ハッシュテヌブルが完党に1぀のUNDEFセルで構成されおいる可胜性があるこずを芚えおおく必芁がありたす。 倚くの芁玠を远加および削陀するず、テヌブルが断片化されたす。 ただし、芁玠の順序を保持するために、 arDataの最埌にのみ新しいものをすべお远加し、結果のスペヌスには挿入したせん。 そのため、 arDataの最埌にarDataするず状況が発生する可胜性がありarData 、UNDEFセルはただ存圚しおいたす。

非垞に断片化された8セルハッシュテヌブルの䟋



芚えおいるように、新しい倀はUNDEFセルに保存できたせん。 䞊蚘のスキヌムでは、ハッシュテヌブルを反埩するずきに、 arData[0]からarData[7]たす。

サむズを倧きくするず、 arDataベクトルを枛らし、デヌタを再配垃するだけで最終的に空のセルを埋めるこずができたす。 テヌブルにサむズ倉曎のコマンドが䞎えられるず、最初にテヌブル自䜓を圧瞮しようずしたす。 次に、圧瞮埌に再床増加する必芁があるかどうかを蚈算したす。 そしお、む゚スず刀明した堎合、テヌブルは2倍になりたす。 その埌、 arDataベクトルは2倍のメモリ(realloc()) arData始めたす。 増やす必芁がない堎合、デヌタは既にメモリにあるセルに再配垃されたす。 ここでは、芁玠を削陀するたびに䜿甚できないアルゎリズムを䜿甚したす。これは、あたりにも頻繁にプロセッサリ゜ヌスを消費し、排気量がそれほど倧きくないためです。 有名なプログラマヌがプロセッサヌずメモリヌの間で劥協したこずを芚えおいたすか

この図は、圧瞮埌の以前の断片化されたハッシュテヌブルを瀺しおいたす。



アルゎリズムはarDataをarData 、各UNDEFセルに次の非UNDEFセルからのデヌタを取り蟌みたす。 簡略化するず、次のようになりたす。

 Bucket *p; uint32_t nIndex, i; HT_HASH_RESET(ht); i = 0; p = ht->arData; do { if (UNEXPECTED(Z_TYPE(p->val) == IS_UNDEF)) { uint32_t j = i; Bucket *q = p; while (++i < ht->nNumUsed) { p++; if (EXPECTED(Z_TYPE_INFO(p->val) != IS_UNDEF)) { ZVAL_COPY_VALUE(&q->val, &p->val); q->h = p->h; nIndex = q->h | ht->nTableMask; q->key = p->key; Z_NEXT(q->val) = HT_HASH(ht, nIndex); HT_HASH(ht, nIndex) = HT_IDX_TO_HASH(j); if (UNEXPECTED(ht->nInternalPointer == i)) { ht->nInternalPointer = j; } q++; j++; } } ht->nNumUsed = j; break; } nIndex = p->h | ht->nTableMask; Z_NEXT(p->val) = HT_HASH(ht, nIndex); HT_HASH(ht, nIndex) = HT_IDX_TO_HASH(i); p++; } while (++i < ht->nNumUsed); 

ハッシュテヌブルAPI


さお、これでPHP 7でハッシュテヌブルを実装する䞻なポむントがわかりたした。次に、パブリックAPIを芋おみたしょう。

特別なこずは䜕もありたせんPHP 5では、APIの方がはるかに優れおいたす。 API関数を䜿甚するずきは、次の3぀のこずを忘れないでください。



キヌ、小文字、敎数にかかわらず、䞻なこず小文字のキヌにはzend_stringからのハッシュがzend_string 、敎数はすぐにハッシュずしお䜿甚されるこずをAPIが認識する必芁がありたす。 したがっお、 zend_hash_add(ht, zend_string, data)たたはzend_hash_index_add(ht, long, data)を満たすこずができたす。

キヌは単玔なペアchar * / intになる堎合がありたす。 この堎合、 zend_hash_str_add(ht, char *, int, data)などの別のAPIを䜿甚する必芁がありたす。 ハッシュテヌブルはzend_stringにアクセスし、文字列キヌに倉換しおハッシュを蚈算し、ある皋床のプロセッサリ゜ヌスを消費するこずにzend_stringしおください。 zend_stringを䜿甚できる堎合は、䜿甚したす。 ハッシュはすでに蚈算されおいるので、APIはそれらを取埗したす。 たずえば、PHPコンパむラは、 zend_stringように、䜿甚される文字列の各郚分のハッシュを蚈算したす。 OPCacheは同様のハッシュを共有メモリに保存したす。 拡匵機胜の䜜成者ずしお、すべおのzend_stringリテラルをzend_string初期化するこずをお勧めしたす。

次に、ハッシュテヌブルに保存するデヌタに぀いお説明したす。 繰り返しになりたすが、ハッシュテヌブルは各Bucket zvalにデヌタを配眮したす。 PHP 7では、zvalはあらゆる皮類のデヌタを保存できたす。 䞀般に、ハッシュテヌブルAPIは、デヌタをzvalにパックするこずを期埅しおいたすzvalは、APIが倀ずしお認識したす。

ストレヌゞたたはメモリ領域ぞのポむンタヌポむンタヌによっお参照されるデヌタがある堎合、状況は倚少単玔化できたす。 次に、APIはこのポむンタヌたたはメモリ領域をzvalに配眮し、zval自䜓がポむンタヌをデヌタずしお䜿甚したす。

䟋は、アむデアを理解するのに圹立ちたす。

 zend_hash_str_add_mem(hashtable *, char *, size_t, void *) zend_hash_index_del(hashtable *, zend_ulong) zend_hash_update_ptr(hashtable *, zend_string *, void *) zend_hash_index_add_empty_element(hashtable *, zend_ulong) 

デヌタを取埗するず、zval *たたはNULLが取埗されたす。 ポむンタヌが倀ずしお䜿甚される堎合、APIはそのたた返されたす。

 zend_hash_index_find(hashtable *, zend_string *) : zval * zend_hash_find_ptr(hashtable *, zend_string *) : void * zend_hash_index_find(hashtable *, zend_ulong) : zval * 

zend_hash_add_new()ような_new APIに぀いおは、䜿甚しない方が良いでしょう。 内郚のニヌズに゚ンゞンを䜿甚したす。 このAPIは、ハッシュ同じキヌで既に䜿甚可胜な堎合でも、ハッシュテヌブルにデヌタを保存させたす。 その結果、重耇が生じ、䜜業に最適な効果が埗られない堎合がありたす。 したがっお、远加しようずしおいるハッシュにデヌタがないこずを完党に確信しおいる堎合にのみ、このAPIを䜿甚できたす。 したがっお、それらを探す必芁はありたせん。

: 5, API zend_symtable_api() :

 static zend_always_inline zval *zend_symtable_update(HashTable *ht, zend_string *key, zval *pData) { zend_ulong idx; if (ZEND_HANDLE_NUMERIC(key, idx)) { /* handle numeric key */ return zend_hash_index_update(ht, idx, pData); } else { return zend_hash_update(ht, key, pData); } } 

, : , zval
 ZEND_HASH_FOREACH :

 #define ZEND_HASH_FOREACH(_ht, indirect) do { \ Bucket *_p = (_ht)->arData; \ Bucket *_end = _p + (_ht)->nNumUsed; \ for (; _p != _end; _p++) { \ zval *_z = &_p->val; \ if (indirect && Z_TYPE_P(_z) == IS_INDIRECT) { \ _z = Z_INDIRECT_P(_z); \ } \ if (UNEXPECTED(Z_TYPE_P(_z) == IS_UNDEF)) continue; #define ZEND_HASH_FOREACH_END() \ } \ } while (0) 

« -»


, : arData , , arData . -: - arData . - , .

: . arData , . , .

« -» (packed hashtable):



, . arData[0] . , 2 * uint32 = 8 . . , , .

: , , ( /), - . bucket'.

 ZEND_API void ZEND_FASTCALL zend_hash_packed_to_hash(HashTable *ht) { void *new_data, *old_data = HT_GET_DATA_ADDR(ht); Bucket *old_buckets = ht->arData; ht->u.flags &= ~HASH_FLAG_PACKED; new_data = pemalloc(HT_SIZE_EX(ht->nTableSize, -ht->nTableSize), (ht)->u.flags & HASH_FLAG_PERSISTENT); ht->nTableMask = -ht->nTableSize; HT_SET_DATA_ADDR(ht, new_data); memcpy(ht->arData, old_buckets, sizeof(Bucket) * ht->nNumUsed); pefree(old_data, (ht)->u.flags & HASH_FLAG_PERSISTENT); zend_hash_rehash(ht); } 

- u.flags , , -. , -, . , . 䟋

 static zend_always_inline zval *_zend_hash_index_add_or_update_i(HashTable *ht, zend_ulong h, zval *pData, uint32_t flag ZEND_FILE_LINE_DC) { uint32_t nIndex; uint32_t idx; Bucket *p; /* ... */ if (UNEXPECTED(!(ht->u.flags & HASH_FLAG_INITIALIZED))) { CHECK_INIT(ht, h < ht->nTableSize); if (h < ht->nTableSize) { p = ht->arData + h; goto add_to_packed; } goto add_to_hash; } else if (ht->u.flags & HASH_FLAG_PACKED) { /* ... */ } else if (EXPECTED(h < ht->nTableSize)) { p = ht->arData + h; } else if ((h >> 1) < ht->nTableSize && (ht->nTableSize >> 1) < ht->nNumOfElements) { zend_hash_packed_grow(ht); p = ht->arData + h; } else { goto convert_to_hash; } /* ... */ 

: - , .


(, 42 60), - «». ( — ) . - API:

 void ZEND_FASTCALL zend_hash_real_init(HashTable *ht, zend_bool packed) 

, zend_hash_real_init() — , «» ( zend_hash_init() ). , «», - . , .


, - .

-


(packed array):

 function m() { printf("%d\n", memory_get_usage()); } $a = range(1,20000); /* range()    */ m(); for($i=0; $i<5000; $i++) { /*      , *       : *    «»  */ $a[] = $i; } m(); /*         * -   «»,   *     */ $a['foo'] = 'bar'; m(); 

, :

 1406744 1406776 1533752 

130 ( 25 000 ).

:

 function m() { printf("%d\n", memory_get_usage()); } /*  -    .   *   32 768  (2^15).    */ for ($i=0; $i<32768; $i++) { $a[$i] = $i; } m(); /*    */ for ($i=0; $i<32768; $i++) { unset($a[$i]); } m(); /*   .  -  , *        */ $a[] = 42; m(); 

結果

 1406864 1406896 1533872 

, ( , modulo noise). unset() arData 32 768 , UNDEF-zval'.

- . nNumUsed , arData ? , , .

?

— , UNDEF-. : , , , . , , , .

 /*   ,   , : */ /*          (idx). *   ,      */ $a[3] = 42; m(); 

:

 1406864 1406896 1406896 

? 32 768 65 538 , . 32 767 . Bucket , zval , long ( 42), . zval long. :) , 32 768 , , , . , , . ., , UNDEF-zval' «» .

-, . , , , . «» , ( idx 0 ), — UNDEF-zval.

 function m() { printf("%d\n", memory_get_usage()); } /*  -    .   *   32 768  (2^15).       */ for ($i=0; $i<32768; $i++) { /*   ,       */ $a[' ' . $i] = $i; } m(); /*    */ for ($i=0; $i<32768; $i++) { unset($a[' ' . $i]); } m(); /*         . *   ,      */ $a[] = 42; m(); 

:

 2582480 1533936 1533936 

. 2,5 . unset() , . 32 768 zend_string , 1,5 .

, , . , , . 42 idx 0, . 物語の終わり。

, - , . ? , (, ?) / , . . , . «» 20 32 , .

(Immutable arrays)


— OPCache. OPCache, , . . . OPCache , . , AST-. , — AST-. 䟋

 $a = ['foo', 1, 'bar']; 

$a — AST-. , , . OPCache AST-, ( ) , . OPCache .

, OPCache , IS_ARRAY_IMMUTABLE IS_TYPE_IMMUTABLE . IS_IMMUTABLE , . , . .

:

 $ar = []; for ($i = 0; $i < 1000000; ++$i) { $ar[] = [1, 2, 3, 4, 5, 6, 7, 8]; } 

400 OPCache, — 35 . OPCache , 8- $ar . 8- . OPCache, 8- IS_IMMUTABLE $ar , .

, , , $ar[42][3] = 'foo'; , 8- $ar[42] .

- , . , PHP- — - Zend. PHP, . - . , / . -. OPArray ( ) ( ). PHP , OPArray: . OPCache IMMUTABLE , . , .

OPCache , . , ( , ). , - OPCache , PHP- . PHP.

. , :

 $a = [1]; $b = [1]; 

. , ( interned strings ), , . — , ( , , ), runtime' PHP. ( ). , OPCache.

Source: https://habr.com/ru/post/J308240/


All Articles