Share
Embed
Transcript
160 slides
-
meihei / 江間 洋平 株式会社PR TIMES Backend Engineer (PHP/Python/Go) X:
@app1e_s GitHub: @meihei3 Bluesky: @meihei.bsky.social #BLEACH #VTuber(どラ、ぶいぱい、個人) #クラロワ #モンハンNOW #秋競馬 etc. 自己紹介 2 -
PHPの配列 $phpcon2024 = [ 'January' => 'phpcon-hokkaido-2024', 'February' => 'phpcon-kansai2024',
'March' => 'phperkaigi-2024', 'April' => 'phpconodawara-2024', 'May' => 'phpconkagawa-2024', ]; 11 -
順番付けられた $phpcon2024 = [ 'January' => 'phpcon-hokkaido-2024', 'February' => 'phpcon-kansai2024',
'March' => 'phperkaigi-2024', 'April' => 'phpconodawara-2024', 'May' => 'phpconkagawa-2024', ]; • 追加された順番を担保している ① ② ③ ④ ⑤ next next next next 12 -
マップ $phpcon2024 = [ 'January' => 'phpcon-hokkaido-2024', 'February' => 'phpcon-kansai2024',
'March' => 'phperkaigi-2024', 'April' => 'phpconodawara-2024', 'May' => 'phpconkagawa-2024', ]; • キーによって値が取り出せるデータ構造 • 連想配列・辞書などとも呼ばれている キーと値がペアになってる 13 -
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 -
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 -
C言語の配列 配列とは、同一の型のデータを メモリ上に一列に並べたもの。 (例) int arr[] = {1, 2, 3};
であれば、 12 bytes の領域に 4 bytes の int 型 のデータが 3 つ並べられる。 20 -
ポインタ アドレスを記憶する変数のこと ポインタに*をつける事で、アド レスが示す値にアクセスできる (例) int x = 0x20241008; int
*p = &x; であれば、p は int へのポインタ型とな り、 p には x のアドレスが保存されてい る。 21 -
配列とポインタ 配列はその先頭要素への ポインタに読み替えられる • &arr[0] と arr は同義 • arr[0]
と *arr は同義 • arr[1] と *(arr+1) は同義 22 -
ポインタの操作 ポインタ演算は整数演算とは異なり、型のサイズ分アド レスを移動する。 (例) int *x; と char *s; のポインタ操作の比較
sizeof(int) = 4 bytes sizeof(char) = 1 byte 23 -
条件付きコンパイル 条件に基づきコードをコンパイルするか、除外するかを 決定することができる。 • #if と #endif ディレクティブを用いる • #elif
や #else で複数の条件にも対応できる 25 -
共用体(union) 複数の異なるデータ型を1つのメモリ領域に格納するた めのデータ構造。 (例) 3 つの型から成る共用体 union example { int32_t
i; int *ptr; char str[8]; } 27 -
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 -
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 -
• PHPは魔法ではない • 一つ一つ見ていけばコンピューターサイエンス の知識で説明可能 (脱線)PHPの配列もメモリ上に並ぶ $phpcon2024 = [ 'January'
=> 'phpcon-hokkaido-2024', 'February' => 'phpcon-kansai2024', 'March' => 'phperkaigi-2024', 'April' => 'phpconodawara-2024', 'May' => 'phpconkagawa-2024', ]; ※この配列はイメージです。実際は4bytesではありません。 31 -
• GDB を使ったデバッグ ◦ 王道だけど、個人的には難しい。 • PHP Extension によるプリントデバッグ ◦
PHP Extension から直接変数へアクセスする php-src のデバッグ 35 -
• 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 -
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 -
データの配列へのポインタは • Bucket の配列: arData • zval の配列: arPacked を選択できる共用体となってい
る。 (タイトル回収)2つの配列 Use more compact representation for packed arrays. #7491 54 -
通常のHashTable(PHPコード) $phpcon2024 = [ 'January' => 'phpcon-hokkaido-2024', 'February' => 'phpcon-kansai2024',
'March' => 'phperkaigi-2024', 'April' => 'phpconodawara-2024', 'May' => 'phpconkagawa-2024', ]; • キーを指定して、値を取り出す • Packed Arrayではないものは全てこれ 57 -
Bucket structure typedef struct _Bucket { zval val; zend_ulong h;
zend_string *key; } Bucket; 62 -
• 配列の値の部分 • zval 型 • sizeof: 16 bytes (zval構造体については後述)
Bucket structure 64 -
• 文字列キーのハッシュ値 • 数値キーの値 • zend_ulong 型 • sizeof: 8
bytes Bucket structure 65 -
• 文字列キーへのポインタ ◦ 数値キーの場合はNULL • zend_string 型 • sizeof: 8
bytes (zend_string構造体は後述) Bucket structure 66 -
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 -
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 -
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 -
zend_string structure struct _zend_string { zend_refcounted_h gc; zend_ulong h; size_t
len; char val[1]; }; 70 -
zend_string structure struct _zend_string { zend_refcounted_h gc; zend_ulong h; size_t
len; char val[1]; }; 文字列のハッシュ値 71 -
zend_string structure struct _zend_string { zend_refcounted_h gc; zend_ulong h; size_t
len; char val[1]; }; 文字列の長さ(\0を含めない) 72 -
zend_string structure struct _zend_string { zend_refcounted_h gc; zend_ulong h; size_t
len; char val[1]; }; 文字列(終端に\0を含めている) 73 -
Packed Array(PHPコード) $phpcon2024 = [ 'phpcon-hokkaido-2024', 'phpcon-kansai2024', 'phperkaigi-2024', 'phpconodawara-2024', 'phpconkagawa-2024',
]; • キーが0からの昇順の整数となっている配列 • キーがそのまま配列の添字となる 75 -
Packed Array(PHPコード) $phpcon2024 = [ 0 => 'phpcon-hokkaido-2024', 1 =>
'phpcon-kansai2024', 2 => 'phperkaigi-2024', 3 => 'phpconodawara-2024', 4 => 'phpconkagawa-2024', ]; • キーが0からの昇順の整数となっている配列 • キーがそのまま配列の添字となる 76 -
PHPの配列が内部的に持つ2種類の配列 • ht->arData でアクセスする Bucket 構造体の配列 • キーからハッシュ表を用いて、データの 配列の添字としてアクセス 通常のHashTable
Packed Array • ht->arPacked でアクセスする zval 構造体の配列 • キーをそのままデータの配列の添字とし てアクセス 81 -
配列の基本的な操作 • 要素の探索、追加、更新は基本的に同じ関数を 呼び出している。 ◦ 引数にフラグを使って条件分岐 • キーが文字列か数値かで変わる ◦ _zend_hash_str_add_or_update_i(...)
◦ _zend_hash_index_add_or_update_i(...) • 削除は _zend_hash_packed_del_val(...) _zend_hash_del_el_ex(...) 84 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
要素の追加 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 -
要素の追加 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 -
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 -
要素の追加(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 -
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 -
要素の削除 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 -
要素の削除 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 -
要素の削除 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 -
要素の削除 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 -
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 -
キーと値のペアの要素の追加 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 -
キーと値のペアの要素の追加 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 -
• 探索:ht->arPacked + h • 追加:ht->arPacked + h に与えられた zval
をコピー • 削除:ht->arPacked + h を UNDEF に置き換える • キーと値のペアで追加:現在の Packed Array の要素が Bucket の配列にコピーされ、通常の HashTable へ変換 される。 Packed Array の操作 121 -
通常のHashTable(PHPコード) // 1. 配列の初期化 $phpcon2024 = [ 'January' => 'phpcon-hokkaido-2024',
]; // 2. 要素の追加 $phpcon2024['February*'] = 'phpcon-kansai2024'; // 3. 要素の削除 unset($phpcon2024['January']); 124 -
通常のHashTable(PHPコード) // 1. 配列の初期化 $phpcon2024 = [ 'January' => 'phpcon-hokkaido-2024',
]; // 2. 要素の追加 $phpcon2024['February*'] = 'phpcon-kansai2024'; // 3. 要素の削除 unset($phpcon2024['January']); 125 -
通常のHashTable(PHPコード) // 1. 配列の初期化 $phpcon2024 = [ 'January' => 'phpcon-hokkaido-2024',
]; // 2. 要素の追加 $phpcon2024['February*'] = 'phpcon-kansai2024'; // 3. 要素の削除 unset($phpcon2024['January']); 128 -
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 -
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 -
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 -
通常のHashTable(PHPコード) // 1. 配列の初期化 $phpcon2024 = [ 'January' => 'phpcon-hokkaido-2024',
]; // 2. 要素の追加 $phpcon2024['February*'] = 'phpcon-kansai2024'; // 3. 要素の削除 unset($phpcon2024['January']); 136 -
要素の削除 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 -
要素の削除 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 -
要素の削除 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 -
要素の削除 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 -
要素の削除 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 -
要素の削除 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 -
• 探索:h (ハッシュ値 or 数値) からビットマスクしてハッ シュ表に保存されている添字を取り出し、Bucket 配列に アクセスする。衝突している場合は Z_NEXT
で次の要素 にチェインする。 • 追加:Bucket 配列に要素を追加し、ハッシュ表上で衝突 する場合は Z_NEXT で1つ前の Bucket を設定する • 削除:指定された Bucket を UNDEF に置き換える。 Z_NEXT で指定されていた場合は、それをなくす。 通常の HashTable の操作 146 -
まとめ • PHPの配列の内部データの配列は2種類ある ◦ Packed Array と、それ以外の通常の HashTable ◦ zval
の配列か、Bucket の配列かの違い • 配列の操作は、2つの配列で実装が異なる 148 -
• 探索:ht->arPacked + h • 追加:ht->arPacked + h に与えられた zval
をコピー • 削除:ht->arPacked + h を UNDEF に置き換える • キーと値のペアで追加:現在の Packed Array の要素が Bucket の配列にコピーされ、通常の HashTable へ変換 される。 (再掲)Packed Array の操作 149 -
• 探索:h (ハッシュ値 or 数値) からビットマスクしてハッ シュ表に保存されている添字を取り出し、Bucket 配列に アクセスする。衝突している場合は Z_NEXT
で次の要素 にチェインする。 • 追加:Bucket 配列に要素を追加し、ハッシュ表上で衝突 する場合は Z_NEXT で1つ前の Bucket を設定する • 削除:指定された Bucket を UNDEF に置き換える。 Z_NEXT で指定されていた場合は、それをなくす。 (再掲)通常の HashTable の操作 150 -
• 配列の構造体やマクロ ◦ zend_type.h • 配列の操作 ◦ zend_hash.h ◦ zend_hash.c
実際、どのファイルを読めばいいの? 153 -
• 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 -
• おそらくメモリの節約と CPU キャッシュ戦略 • メモリの節約 ◦ arHash を別の配列として用意すると、それを参照す るポインタを別途用意する必要がある。
なので、その分 64 (32) bits の節約ができる。 • CPU キャッシュ戦略 ◦ arData と arHash がメモリ上の近い場所にあること で、空間的局所性を利用しキャッシュに乗りやすく なる(と思われる) 何故、ハッシュ表はマイナスでアクセスするの? 155 -
• 変数をメモリに配置する際、CPUの環境によってバ イト列の並べ方が異なる ◦ これを「バイトオーダー」と呼ぶ ◦ 上位バイトから順に配置されるビッグエンディアン ◦ 上位バイトから逆順に配置されるリトルエンディアン •
uint32_tとuint8_tが4つの構造体を共用体とすると き、uint32_tのバイトオーダーによって構造体から のアクセスが意図せぬものになることがある ◦ それを防ぐためにバイトオーダーによって構造体の定 義の順番を変えている(文マクロ) ZEND_ENDIAN_LOHI_~ は何をしているの? 157 -
• List とはキーが0からの連番になった配列 • Packed Array は HASH_FLAG_PACKED が有効な配 列
◦ 一度 zend_hash_packed_to_hash が呼ばれると基本 的には HT_IS_PACKED は FALSE になる • 条件によっては List であってもハッシュ表を用いた Bucket の配列であることもある Packed Array ≠ List 158 -
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 -
参考 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