Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PHP8.2から見る、2つの配列 / PHP Conference Japan 2023

meihei
October 08, 2023

PHP8.2から見る、2つの配列 / PHP Conference Japan 2023

meihei

October 08, 2023
Tweet

More Decks by meihei

Other Decks in Programming

Transcript

  1. meihei / 江間 洋平 株式会社PR TIMES Backend Engineer (PHP/Python/Go) X:

    @app1e_s GitHub: @meihei3 Bluesky: @meihei.bsky.social #BLEACH #VTuber(どラ、ぶいぱい、個人) #クラロワ #モンハンNOW #秋競馬 etc. 自己紹介 2
  2. PHPの配列 $phpcon2024 = [ 'January' => 'phpcon-hokkaido-2024', 'February' => 'phpcon-kansai2024',

    'March' => 'phperkaigi-2024', 'April' => 'phpconodawara-2024', 'May' => 'phpconkagawa-2024', ]; 11
  3. 順番付けられた $phpcon2024 = [ 'January' => 'phpcon-hokkaido-2024', 'February' => 'phpcon-kansai2024',

    'March' => 'phperkaigi-2024', 'April' => 'phpconodawara-2024', 'May' => 'phpconkagawa-2024', ]; • 追加された順番を担保している ① ② ③ ④ ⑤ next next next next 12
  4. マップ $phpcon2024 = [ 'January' => 'phpcon-hokkaido-2024', 'February' => 'phpcon-kansai2024',

    'March' => 'phperkaigi-2024', 'April' => 'phpconodawara-2024', 'May' => 'phpconkagawa-2024', ]; • キーによって値が取り出せるデータ構造 • 連想配列・辞書などとも呼ばれている キーと値がペアになってる 13
  5. php-srcのコードの一部 union { struct { ZEND_ENDIAN_LOHI_4( uint8_t flags, uint8_t _unused,

    uint8_t nIteratorsCount, uint8_t _unused2 ) } v; uint32_t flags; } u; 16
  6. php-srcのコードの一部 #if SIZEOF_SIZE_T == 4 # define HT_HASH_TO_BUCKET_EX(data, idx) \

    ((Bucket*)((char*)(data) + (idx))) #elif SIZEOF_SIZE_T == 8 # define HT_HASH_TO_BUCKET_EX(data, idx) \ ((data) + (idx)) #else # error "Unknown SIZEOF_SIZE_T" #endif 17
  7. C言語の配列 配列とは、同一の型のデータを メモリ上に一列に並べたもの。 (例) int arr[] = {1, 2, 3};

    であれば、 12 bytes の領域に 4 bytes の int 型 のデータが 3 つ並べられる。 20
  8. ポインタ アドレスを記憶する変数のこと ポインタに*をつける事で、アド レスが示す値にアクセスできる (例) int x = 0x20241008; int

    *p = &x; であれば、p は int へのポインタ型とな り、 p には x のアドレスが保存されてい る。 21
  9. php-srcのコードの一部 union { struct { ZEND_ENDIAN_LOHI_4( uint8_t flags, uint8_t _unused,

    uint8_t nIteratorsCount, uint8_t _unused2 ) } v; uint32_t flags; } u; 共用体 文マクロの使用 構造体 29
  10. php-srcのコードの一部 #if SIZEOF_SIZE_T == 4 # define HT_HASH_TO_BUCKET_EX(data, idx) \

    ((Bucket*)((char*)(data) + (idx))) #elif SIZEOF_SIZE_T == 8 # define HT_HASH_TO_BUCKET_EX(data, idx) \ ((data) + (idx)) #else # error "Unknown SIZEOF_SIZE_T" #endif 条件付きコンパイル 関数マクロの定義 30
  11. • PHPは魔法ではない • 一つ一つ見ていけばコンピューターサイエンス の知識で説明可能 (脱線)PHPの配列もメモリ上に並ぶ $phpcon2024 = [ 'January'

    => 'phpcon-hokkaido-2024', 'February' => 'phpcon-kansai2024', 'March' => 'phperkaigi-2024', 'April' => 'phpconodawara-2024', 'May' => 'phpconkagawa-2024', ]; ※この配列はイメージです。実際は4bytesではありません。 31
  12. • PHP Extension の開発環境の整備 ◦ 雛形を利用する(zeriyoshi/pskel) • 欲しい情報を print する

    ◦ GitHub Copilot と ChatGPT を駆使する PHP Extension の開発 PHP Extension の開発については 13:45〜 トラック6 で発表があるので、 是非聞いてみてください! https://fortee.jp/phpcon-2023/proposal/747ee16f-31 69-4094-be4f-b5b558273ea9 36
  13. HashTable structure struct _zend_array { zend_refcounted_h gc; union { struct

    { ZEND_ENDIAN_LOHI_4( uint8_t flags, uint8_t _unused, uint8_t nIteratorsCount, uint8_t _unused2) } v; uint32_t flags; } u; uint32_t nTableMask; union { uint32_t *arHash; Bucket *arData; zval *arPacked; }; uint32_t nNumUsed; uint32_t nNumOfElements; uint32_t nTableSize; uint32_t nInternalPointer; zend_long nNextFreeElement; dtor_func_t pDestructor; }; 42
  14. データの配列へのポインタは • Bucket の配列: arData • zval の配列: arPacked を選択できる共用体となってい

    る。 (タイトル回収)2つの配列 Use more compact representation for packed arrays. #7491 54
  15. 通常のHashTable(PHPコード) $phpcon2024 = [ 'January' => 'phpcon-hokkaido-2024', 'February' => 'phpcon-kansai2024',

    'March' => 'phperkaigi-2024', 'April' => 'phpconodawara-2024', 'May' => 'phpconkagawa-2024', ]; • キーを指定して、値を取り出す • Packed Arrayではないものは全てこれ 57
  16. zval structure struct _zval_struct { zend_value value; union { uint32_t

    type_info; struct { ZEND_ENDIAN_LOHI_3( uint8_t type, uint8_t type_flags, union { uint16_t extra; } u) } v; } u1; union { uint32_t next; uint32_t cache_slot; uint32_t opline_num; uint32_t lineno; uint32_t num_args; uint32_t fe_pos; uint32_t fe_iter_idx; uint32_t guard; uint32_t constant_flags; uint32_t extra; } u2; }; 67
  17. zval structure struct _zval_struct { zend_value value; union { uint32_t

    type_info; struct { ZEND_ENDIAN_LOHI_3( uint8_t type, uint8_t type_flags, union { uint16_t extra; } u) } v; } u1; union { uint32_t next; uint32_t cache_slot; uint32_t opline_num; uint32_t lineno; uint32_t num_args; uint32_t fe_pos; uint32_t fe_iter_idx; uint32_t guard; uint32_t constant_flags; uint32_t extra; } u2; }; 実際の値を保存する共用体 68
  18. zval structure struct _zval_struct { zend_value value; union { uint32_t

    type_info; struct { ZEND_ENDIAN_LOHI_3( uint8_t type, uint8_t type_flags, union { uint16_t extra; } u) } v; } u1; union { uint32_t next; uint32_t cache_slot; uint32_t opline_num; uint32_t lineno; uint32_t num_args; uint32_t fe_pos; uint32_t fe_iter_idx; uint32_t guard; uint32_t constant_flags; uint32_t extra; } u2; }; 情報やフラグを管理する領域 69
  19. zend_string structure struct _zend_string { zend_refcounted_h gc; zend_ulong h; size_t

    len; char val[1]; }; 文字列の長さ(\0を含めない) 72
  20. zend_string structure struct _zend_string { zend_refcounted_h gc; zend_ulong h; size_t

    len; char val[1]; }; 文字列(終端に\0を含めている) 73
  21. Packed Array(PHPコード) $phpcon2024 = [ 'phpcon-hokkaido-2024', 'phpcon-kansai2024', 'phperkaigi-2024', 'phpconodawara-2024', 'phpconkagawa-2024',

    ]; • キーが0からの昇順の整数となっている配列 • キーがそのまま配列の添字となる 75
  22. Packed Array(PHPコード) $phpcon2024 = [ 0 => 'phpcon-hokkaido-2024', 1 =>

    'phpcon-kansai2024', 2 => 'phperkaigi-2024', 3 => 'phpconodawara-2024', 4 => 'phpconkagawa-2024', ]; • キーが0からの昇順の整数となっている配列 • キーがそのまま配列の添字となる 76
  23. PHPの配列が内部的に持つ2種類の配列 • ht->arData でアクセスする Bucket 構造体の配列 • キーからハッシュ表を用いて、データの 配列の添字としてアクセス 通常のHashTable

    Packed Array • ht->arPacked でアクセスする zval 構造体の配列 • キーをそのままデータの配列の添字とし てアクセス 81
  24. Packed Array(PHPコード) // 1. 配列の初期化 $phpcon2024 = [ 'phpcon-hokkaido-2024', ];

    // 2. 要素の追加 $phpcon2024[] = 'phpcon-kansai2024'; // 3. 要素の削除 unset($phpcon2024[0]); // 4. 要素の追加(Packed Arrayを通常のHashTableへ) $phpcon2024['March'] = 'phperkaigi-2024'; 88
  25. Packed Array(PHPコード) // 1. 配列の初期化 $phpcon2024 = [ 'phpcon-hokkaido-2024', ];

    // 2. 要素の追加 $phpcon2024[] = 'phpcon-kansai2024'; // 3. 要素の削除 unset($phpcon2024[0]); // 4. 要素の追加(Packed Arrayを通常のHashTableへ) $phpcon2024['March'] = 'phperkaigi-2024'; 89
  26. Packed Array(PHPコード) // 1. 配列の初期化 $phpcon2024 = [ 'phpcon-hokkaido-2024', ];

    // 2. 要素の追加 $phpcon2024[] = 'phpcon-kansai2024'; // 3. 要素の削除 unset($phpcon2024[0]); // 4. 要素の追加(Packed Arrayを通常のHashTableへ) $phpcon2024['March'] = 'phperkaigi-2024'; HashTableの生成、Packed Arrayの初期化、要素の追加、 の3つを行っている 90
  27. HashTableの生成 ZEND_API HashTable* ZEND_FASTCALL _zend_new_array( uint32_t nSize) { HashTable *ht

    = emalloc(sizeof(HashTable)); _zend_hash_init_int(ht, nSize, ZVAL_PTR_DTOR, 0); return ht; } 91
  28. HashTableの生成 ZEND_API HashTable* ZEND_FASTCALL _zend_new_array( uint32_t nSize) { HashTable *ht

    = emalloc(sizeof(HashTable)); _zend_hash_init_int(ht, nSize, ZVAL_PTR_DTOR, 0); return ht; } 92
  29. HashTableの生成 static zend_always_inline void _zend_hash_init_int(HashTable *ht, uint32_t nSize, dtor_func_t pDestructor,

    bool persistent) { GC_SET_REFCOUNT(ht, 1); GC_TYPE_INFO(ht) = GC_ARRAY | (persistent ? ((GC_PERSISTENT|GC_NOT_COLLECTABLE) << GC_FLAGS_SHIFT) : 0); HT_FLAGS(ht) = HASH_FLAG_UNINITIALIZED; ht->nTableMask = HT_MIN_MASK; HT_SET_DATA_ADDR(ht, &uninitialized_bucket); ht->nNumUsed = 0; ht->nNumOfElements = 0; ht->nInternalPointer = 0; ht->nNextFreeElement = ZEND_LONG_MIN; ht->pDestructor = pDestructor; ht->nTableSize = zend_hash_check_size(nSize); } 93
  30. Packed Array の初期化 static zend_always_inline void zend_hash_real_init_packed_ex(HashTable *ht) { void

    *data; if (UNEXPECTED(GC_FLAGS(ht) & IS_ARRAY_PERSISTENT)) { data = pemalloc(HT_PACKED_SIZE_EX(ht->nTableSize, HT_MIN_MASK), 1); } else if (EXPECTED(ht->nTableSize == HT_MIN_SIZE)) { data = emalloc(HT_PACKED_SIZE_EX(HT_MIN_SIZE, HT_MIN_MASK)); } else { data = emalloc(HT_PACKED_SIZE_EX(ht->nTableSize, HT_MIN_MASK)); } HT_SET_DATA_ADDR(ht, data); ht->u.v.flags = HASH_FLAG_PACKED | HASH_FLAG_STATIC_KEYS; HT_HASH_RESET_PACKED(ht); } 95
  31. 要素の追加 static zend_always_inline zval *_zend_hash_index_add_or_update_i(HashTable *ht, zend_ulong h, zval *pData,

    uint32_t flag) { ... if (HT_IS_PACKED(ht)) { ... } else if (EXPECTED(h < ht->nTableSize)) { add_to_packed: zv = ht->arPacked + h; if ((flag & (HASH_ADD_NEW|HASH_ADD_NEXT)) != (HASH_ADD_NEW|HASH_ADD_NEXT)) { if (h > ht->nNumUsed) { zval *q = ht->arPacked + ht->nNumUsed; while (q != zv) { ZVAL_UNDEF(q); q++; } } } ht->nNextFreeElement = ht->nNumUsed = h + 1; ht->nNumOfElements++; if (flag & HASH_LOOKUP) { ZVAL_NULL(zv); } else { ZVAL_COPY_VALUE(zv, pData); } return zv; } else if ... } 100
  32. 要素の追加 ht->nNextFreeElement = ht-> nNumUsed = h + 1; ht->nNumOfElements++;

    if (flag & HASH_LOOKUP) { ZVAL_NULL(zv); } else { ZVAL_COPY_VALUE(zv, pData); } zv = ht->arPacked + h; 101
  33. Packed Array(PHPコード) // 1. 配列の初期化 $phpcon2024 = [ 'phpcon-hokkaido-2024', ];

    // 2. 要素の追加 $phpcon2024[] = 'phpcon-kansai2024'; // 3. 要素の削除 unset($phpcon2024[0]); // 4. 要素の追加(Packed Arrayを通常のHashTableへ) $phpcon2024['March'] = 'phperkaigi-2024'; 102
  34. 要素の追加(2回目) ht->nNextFreeElement = ht-> nNumUsed = h + 1; ht->nNumOfElements++;

    if (flag & HASH_LOOKUP) { ZVAL_NULL(zv); } else { ZVAL_COPY_VALUE(zv, pData); } zv = ht->arPacked + h; 104
  35. Packed Array(PHPコード) // 1. 配列の初期化 $phpcon2024 = [ 'phpcon-hokkaido-2024', ];

    // 2. 要素の追加 $phpcon2024[] = 'phpcon-kansai2024'; // 3. 要素の削除 unset($phpcon2024[0]); // 4. 要素の追加(Packed Arrayを通常のHashTableへ) $phpcon2024['March'] = 'phperkaigi-2024'; 105
  36. 要素の削除 ZEND_API zend_result ZEND_FASTCALL zend_hash_index_del(HashTable *ht, zend_ulong h) { ...

    if (HT_IS_PACKED(ht)) { if (h < ht->nNumUsed) { zval *zv = ht->arPacked + h; if (Z_TYPE_P(zv) != IS_UNDEF) { _zend_hash_packed_del_val(ht, HT_IDX_TO_HASH(h), zv); return SUCCESS; } } return FAILURE; } ... } 106
  37. 要素の削除 ZEND_API zend_result ZEND_FASTCALL zend_hash_index_del(HashTable *ht, zend_ulong h) { ...

    if (HT_IS_PACKED(ht)) { if (h < ht->nNumUsed) { zval *zv = ht->arPacked + h; if (Z_TYPE_P(zv) != IS_UNDEF) { _zend_hash_packed_del_val(ht, HT_IDX_TO_HASH(h), zv); return SUCCESS; } } return FAILURE; } ... } 107
  38. 要素の削除 static zend_always_inline void _zend_hash_packed_del_val(HashTable *ht, uint32_t idx, zval *zv)

    { idx = HT_HASH_TO_IDX(idx); ht->nNumOfElements--; ... if (ht->pDestructor) { zval tmp; ZVAL_COPY_VALUE(&tmp, zv); ZVAL_UNDEF(zv); ht->pDestructor(&tmp); } else { ZVAL_UNDEF(zv); } } 108
  39. 要素の削除 ht->nNumOfElements--; ... if (ht->pDestructor) { zval tmp; ZVAL_COPY_VALUE(&tmp, zv);

    ZVAL_UNDEF(zv); ht->pDestructor(&tmp); } else { ZVAL_UNDEF(zv); } zv = ht->arPacked + h; は UNDEF に置き換えられる 109
  40. Packed Array(PHPコード) // 1. 配列の初期化 $phpcon2024 = [ 'phpcon-hokkaido-2024', ];

    // 2. 要素の追加 $phpcon2024[] = 'phpcon-kansai2024'; // 3. 要素の削除 unset($phpcon2024[0]); // 4. 要素の追加(Packed Arrayを通常のHashTableへ) $phpcon2024['March'] = 'phperkaigi-2024'; 110
  41. キーと値のペアの要素の追加 static zend_always_inline zval *_zend_hash_str_add_or_update_i(HashTable *ht, const char *str, size_t

    len, zend_ulong h, zval *pData, uint32_t flag) { ... if (UNEXPECTED(HT_FLAGS(ht) & (HASH_FLAG_UNINITIALIZED|HASH_FLAG_PACKED))) { if (EXPECTED(HT_FLAGS(ht) & HASH_FLAG_UNINITIALIZED)) { zend_hash_real_init_mixed(ht); goto add_to_hash; } else { zend_hash_packed_to_hash(ht); } } else if ... } 111
  42. キーと値のペアの要素の追加 static zend_always_inline zval *_zend_hash_str_add_or_update_i(HashTable *ht, const char *str, size_t

    len, zend_ulong h, zval *pData, uint32_t flag) { ... if (UNEXPECTED(HT_FLAGS(ht) & (HASH_FLAG_UNINITIALIZED|HASH_FLAG_PACKED))) { if (EXPECTED(HT_FLAGS(ht) & HASH_FLAG_UNINITIALIZED)) { zend_hash_real_init_mixed(ht); goto add_to_hash; } else { zend_hash_packed_to_hash(ht); } } else if ... } これより先は図で説明 112
  43. • 探索:ht->arPacked + h • 追加:ht->arPacked + h に与えられた zval

    をコピー • 削除:ht->arPacked + h を UNDEF に置き換える • キーと値のペアで追加:現在の Packed Array の要素が Bucket の配列にコピーされ、通常の HashTable へ変換 される。 Packed Array の操作 121
  44. 通常のHashTable(PHPコード) // 1. 配列の初期化 $phpcon2024 = [ 'January' => 'phpcon-hokkaido-2024',

    ]; // 2. 要素の追加 $phpcon2024['February*'] = 'phpcon-kansai2024'; // 3. 要素の削除 unset($phpcon2024['January']); 124
  45. 通常のHashTable(PHPコード) // 1. 配列の初期化 $phpcon2024 = [ 'January' => 'phpcon-hokkaido-2024',

    ]; // 2. 要素の追加 $phpcon2024['February*'] = 'phpcon-kansai2024'; // 3. 要素の削除 unset($phpcon2024['January']); 125
  46. 通常のHashTable(PHPコード) // 1. 配列の初期化 $phpcon2024 = [ 'January' => 'phpcon-hokkaido-2024',

    ]; // 2. 要素の追加 $phpcon2024['February*'] = 'phpcon-kansai2024'; // 3. 要素の削除 unset($phpcon2024['January']); 128
  47. 2つ目の要素の追加 add_to_hash: idx = ht->nNumUsed++; ht->nNumOfElements++; p = ht->arData +

    idx; p->key = key = zend_string_init(str, len, GC_FLAGS(ht) & IS_ARRAY_PERSISTENT); p->h = ZSTR_H(key) = h; HT_FLAGS(ht) &= ~HASH_FLAG_STATIC_KEYS; if (flag & HASH_LOOKUP) { ZVAL_NULL(&p->val); } else { ZVAL_COPY_VALUE(&p->val, pData); } nIndex = h | ht->nTableMask; Z_NEXT(p->val) = HT_HASH(ht, nIndex); HT_HASH(ht, nIndex) = HT_IDX_TO_HASH(idx); 129
  48. 2つ目の要素の追加 add_to_hash: idx = ht->nNumUsed++; ht->nNumOfElements++; p = ht->arData +

    idx; p->key = key = zend_string_init(str, len, GC_FLAGS(ht) & IS_ARRAY_PERSISTENT); p->h = ZSTR_H(key) = h; HT_FLAGS(ht) &= ~HASH_FLAG_STATIC_KEYS; if (flag & HASH_LOOKUP) { ZVAL_NULL(&p->val); } else { ZVAL_COPY_VALUE(&p->val, pData); } nIndex = h | ht->nTableMask; Z_NEXT(p->val) = HT_HASH(ht, nIndex); HT_HASH(ht, nIndex) = HT_IDX_TO_HASH(idx); zval.u2.next にハッシュ表上 の添字を保存する 132
  49. 2つ目の要素の追加 add_to_hash: idx = ht->nNumUsed++; ht->nNumOfElements++; p = ht->arData +

    idx; p->key = key = zend_string_init(str, len, GC_FLAGS(ht) & IS_ARRAY_PERSISTENT); p->h = ZSTR_H(key) = h; HT_FLAGS(ht) &= ~HASH_FLAG_STATIC_KEYS; if (flag & HASH_LOOKUP) { ZVAL_NULL(&p->val); } else { ZVAL_COPY_VALUE(&p->val, pData); } nIndex = h | ht->nTableMask; Z_NEXT(p->val) = HT_HASH(ht, nIndex); HT_HASH(ht, nIndex) = HT_IDX_TO_HASH(idx); ハッシュ表上の添字を更新する 134
  50. 通常のHashTable(PHPコード) // 1. 配列の初期化 $phpcon2024 = [ 'January' => 'phpcon-hokkaido-2024',

    ]; // 2. 要素の追加 $phpcon2024['February*'] = 'phpcon-kansai2024'; // 3. 要素の削除 unset($phpcon2024['January']); 136
  51. 要素の削除 ZEND_API zend_result ZEND_FASTCALL zend_hash_del(HashTable *ht, zend_string *key) { ...

    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); if ((p->key == key) || (p->h == h && p->key && zend_string_equal_content(p->key, key))) { zend_string_release(p->key); p->key = NULL; _zend_hash_del_el_ex(ht, idx, p, prev); return SUCCESS; } prev = p; idx = Z_NEXT(p->val); ... 137
  52. 要素の削除 ZEND_API zend_result ZEND_FASTCALL zend_hash_del(HashTable *ht, zend_string *key) { ...

    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); if ((p->key == key) || (p->h == h && p->key && zend_string_equal_content(p->key, key))) { zend_string_release(p->key); p->key = NULL; _zend_hash_del_el_ex(ht, idx, p, prev); return SUCCESS; } prev = p; idx = Z_NEXT(p->val); ... コンフリクトした ハッシュ値から、 目的の要素を探索 138
  53. 要素の削除 ZEND_API zend_result ZEND_FASTCALL zend_hash_del(HashTable *ht, zend_string *key) { ...

    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); if ((p->key == key) || (p->h == h && p->key && zend_string_equal_content(p->key, key))) { zend_string_release(p->key); p->key = NULL; _zend_hash_del_el_ex(ht, idx, p, prev); return SUCCESS; } prev = p; idx = Z_NEXT(p->val); ... 139
  54. 要素の削除 static zend_always_inline void _zend_hash_del_el_ex(HashTable *ht, uint32_t idx, Bucket *p,

    Bucket *prev) { if (prev) { Z_NEXT(prev->val) = Z_NEXT(p->val); } else { HT_HASH(ht, p->h | ht->nTableMask) = Z_NEXT(p->val); } idx = HT_HASH_TO_IDX(idx); ht->nNumOfElements--; ... if (ht->pDestructor) { ... } else { ZVAL_UNDEF(&p->val); } } 140
  55. 要素の削除 static zend_always_inline void _zend_hash_del_el_ex(HashTable *ht, uint32_t idx, Bucket *p,

    Bucket *prev) { if (prev) { Z_NEXT(prev->val) = Z_NEXT(p->val); } else { HT_HASH(ht, p->h | ht->nTableMask) = Z_NEXT(p->val); } idx = HT_HASH_TO_IDX(idx); ht->nNumOfElements--; ... if (ht->pDestructor) { ... } else { ZVAL_UNDEF(&p->val); } } 141
  56. 要素の削除 static zend_always_inline void _zend_hash_del_el_ex(HashTable *ht, uint32_t idx, Bucket *p,

    Bucket *prev) { if (prev) { Z_NEXT(prev->val) = Z_NEXT(p->val); } else { HT_HASH(ht, p->h | ht->nTableMask) = Z_NEXT(p->val); } idx = HT_HASH_TO_IDX(idx); ht->nNumOfElements--; ... if (ht->pDestructor) { ... } else { ZVAL_UNDEF(&p->val); } } 143
  57. • 探索:h (ハッシュ値 or 数値) からビットマスクしてハッ シュ表に保存されている添字を取り出し、Bucket 配列に アクセスする。衝突している場合は Z_NEXT

    で次の要素 にチェインする。 • 追加:Bucket 配列に要素を追加し、ハッシュ表上で衝突 する場合は Z_NEXT で1つ前の Bucket を設定する • 削除:指定された Bucket を UNDEF に置き換える。 Z_NEXT で指定されていた場合は、それをなくす。 通常の HashTable の操作 146
  58. まとめ • PHPの配列の内部データの配列は2種類ある ◦ Packed Array と、それ以外の通常の HashTable ◦ zval

    の配列か、Bucket の配列かの違い • 配列の操作は、2つの配列で実装が異なる 148
  59. • 探索:ht->arPacked + h • 追加:ht->arPacked + h に与えられた zval

    をコピー • 削除:ht->arPacked + h を UNDEF に置き換える • キーと値のペアで追加:現在の Packed Array の要素が Bucket の配列にコピーされ、通常の HashTable へ変換 される。 (再掲)Packed Array の操作 149
  60. • 探索:h (ハッシュ値 or 数値) からビットマスクしてハッ シュ表に保存されている添字を取り出し、Bucket 配列に アクセスする。衝突している場合は Z_NEXT

    で次の要素 にチェインする。 • 追加:Bucket 配列に要素を追加し、ハッシュ表上で衝突 する場合は Z_NEXT で1つ前の Bucket を設定する • 削除:指定された Bucket を UNDEF に置き換える。 Z_NEXT で指定されていた場合は、それをなくす。 (再掲)通常の HashTable の操作 150
  61. • PHP スクリプト実行の流れを知る ◦ PHP による hello world 入門 by

    @sji_ch ▪ https://tech.respect-pal.jp/php-helloworld/ ◦ php-srcを読んでみよう by @tzm_freedom ▪ https://fortee.jp/phpcon-2022/proposal/1addf51 d-6f72-4c96-9337-034ec6cc0643 • php-srcをデバッグビルド、GDBでスタックトレース しながらデバッグ。 PHP スクリプトから配列が呼ばれるまでの流れを知りたい 154
  62. • おそらくメモリの節約と CPU キャッシュ戦略 • メモリの節約 ◦ arHash を別の配列として用意すると、それを参照す るポインタを別途用意する必要がある。

    なので、その分 64 (32) bits の節約ができる。 • CPU キャッシュ戦略 ◦ arData と arHash がメモリ上の近い場所にあること で、空間的局所性を利用しキャッシュに乗りやすく なる(と思われる) 何故、ハッシュ表はマイナスでアクセスするの? 155
  63. • 変数をメモリに配置する際、CPUの環境によってバ イト列の並べ方が異なる ◦ これを「バイトオーダー」と呼ぶ ◦ 上位バイトから順に配置されるビッグエンディアン ◦ 上位バイトから逆順に配置されるリトルエンディアン •

    uint32_tとuint8_tが4つの構造体を共用体とすると き、uint32_tのバイトオーダーによって構造体から のアクセスが意図せぬものになることがある ◦ それを防ぐためにバイトオーダーによって構造体の定 義の順番を変えている(文マクロ) ZEND_ENDIAN_LOHI_~ は何をしているの? 157
  64. • List とはキーが0からの連番になった配列 • Packed Array は HASH_FLAG_PACKED が有効な配 列

    ◦ 一度 zend_hash_packed_to_hash が呼ばれると基本 的には HT_IS_PACKED は FALSE になる • 条件によっては List であってもハッシュ表を用いた Bucket の配列であることもある Packed Array ≠ List 158
  65. Packed Array ≠ List $phpcon2024 = [ 'phpcon-hokkaido-2024', 'phpcon-kansai2024', 4

    => 'phpconkagawa-2024', ]; // bool(false) var_dump(array_is_list( $phpcon2024)); $phpcon2024 = [ 'phpcon-hokkaido-2024', 'phpcon-kansai2024', ]; unset($phpcon2024[1]); $phpcon2024[1] = 'phpcon-kansai2024'; // bool(true) var_dump(array_is_list( $phpcon2024)); Packed Array だが List ではない List だが Packed Array ではない 159
  66. 参考 1. 本物のC http://real-c.info/index.html 2. Go and CPU Caches https://teivah.medium.com/go-and-cpu-caches-af5d32cc5592

    3. PHP による hello world 入門 https://tech.respect-pal.jp/php-helloworld/ 4. PHP7で変わること ——言語仕様とエンジンの改善ポイント https://www.slideshare.net/hnw/phpcon-kansai20150530 5. PHPとPythonとRubyの連想配列のデータ構造が同時期に同じ方針で性能改善されてた話 https://hnw.hatenablog.com/entry/2021/01/10/162018 6. [php-src読書録]その6: array https://blog.freedom-man.com/php_src_6 7. php内核源码分析之HashTable https://fantiq.github.io/2019/05/26/php%E5%86%85%E6%A0%B8%E6%BA%90%E7 %A0%81%E5%88%86%E6%9E%90%E4%B9%8BHashTable/ 8. PHPはどのように動くのか ~PHPコアから読み解く仕組みと定石, 技術評論 社,https://gihyo.jp/book/2015/978-4-7741-7642-0 160