summaryrefslogtreecommitdiffhomepage
path: root/NOTE.md
blob: a2978f64eb1600b46ad99e4a0997a553fbc817ee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
PHP 8.4.3 のソースを基に調査



https://php.github.io/php-src/
    https://php.github.io/php-src/core/data-structures/index.html



https://www.phpinternalsbook.com/



https://www.phpinternalsbook.com/php7/zvals/basic_structure.html

zval
    zend_value + type + meta

zend_value
    union
        zend_refcounted*
        zend_string*
        zend_array*
        zend_object*
        zend_reference*


```c
typedef struct _zval_struct     zval;

struct _zval_struct {
	zend_value        value;			/* value */
	union {
		uint32_t type_info;
		struct {
			ZEND_ENDIAN_LOHI_3(
				uint8_t    type,			/* active type */
				uint8_t    type_flags,
				union {
					uint16_t  extra;        /* not further specified */
				} u)
		} v;
	} u1;
	union {
		uint32_t     next;                 /* hash collision chain */
		uint32_t     cache_slot;           /* cache slot (for RECV_INIT) */
		uint32_t     opline_num;           /* opline number (for FAST_CALL) */
		uint32_t     lineno;               /* line number (for ast nodes) */
		uint32_t     num_args;             /* arguments number for EX(This) */
		uint32_t     fe_pos;               /* foreach position */
		uint32_t     fe_iter_idx;          /* foreach iterator index */
		uint32_t     guard;                /* recursion and single property guard */
		uint32_t     constant_flags;       /* constant flags */
		uint32_t     extra;                /* not further specified */
	} u2;
};

typedef union _zend_value {
	zend_long         lval;				/* long value */
	double            dval;				/* double value */
	zend_refcounted  *counted;
	zend_string      *str;
	zend_array       *arr;
	zend_object      *obj;
	zend_resource    *res;
	zend_reference   *ref;
	zend_ast_ref     *ast;
	zval             *zv;
	void             *ptr;
	zend_class_entry *ce;
	zend_function    *func;
	struct {
		uint32_t w1;
		uint32_t w2;
	} ww;
} zend_value;
```

```c
/* Regular data types: Must be in sync with zend_variables.c. */
#define IS_UNDEF					0
#define IS_NULL						1
#define IS_FALSE					2
#define IS_TRUE						3
#define IS_LONG						4
#define IS_DOUBLE					5
#define IS_STRING					6
#define IS_ARRAY					7
#define IS_OBJECT					8
#define IS_RESOURCE					9
#define IS_REFERENCE				10
#define IS_CONSTANT_AST				11 /* Constant expressions */

/* Fake types used only for type hinting.
 * These are allowed to overlap with the types below. */
#define IS_CALLABLE					12
#define IS_ITERABLE					13
#define IS_VOID						14
#define IS_STATIC					15
#define IS_MIXED					16
#define IS_NEVER					17

/* internal types */
#define IS_INDIRECT             	12
#define IS_PTR						13
#define IS_ALIAS_PTR				14
#define _IS_ERROR					15

/* used for casts */
#define _IS_BOOL					18
#define _IS_NUMBER					19


/* zval_gc_flags(zval.value->gc.u.type_info) (common flags) */
#define GC_NOT_COLLECTABLE			(1<<4)
#define GC_PROTECTED                (1<<5) /* used for recursion detection */
#define GC_IMMUTABLE                (1<<6) /* can't be changed in place */
#define GC_PERSISTENT               (1<<7) /* allocated using malloc */
#define GC_PERSISTENT_LOCAL         (1<<8) /* persistent, but thread-local */

#define GC_NULL						(IS_NULL         | (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT))
#define GC_STRING					(IS_STRING       | (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT))
#define GC_ARRAY					IS_ARRAY
#define GC_OBJECT					IS_OBJECT
#define GC_RESOURCE					(IS_RESOURCE     | (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT))
#define GC_REFERENCE				(IS_REFERENCE    | (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT))
#define GC_CONSTANT_AST				(IS_CONSTANT_AST | (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT))

/* zval.u1.v.type_flags */
#define IS_TYPE_REFCOUNTED			(1<<0)
#define IS_TYPE_COLLECTABLE			(1<<1)

/* string flags (zval.value->gc.u.flags) */
#define IS_STR_CLASS_NAME_MAP_PTR   GC_PROTECTED  /* refcount is a map_ptr offset of class_entry */
#define IS_STR_INTERNED				GC_IMMUTABLE  /* interned string */
#define IS_STR_PERSISTENT			GC_PERSISTENT /* allocated using malloc */
#define IS_STR_PERMANENT        	(1<<8)        /* relives request boundary */
#define IS_STR_VALID_UTF8           (1<<9)        /* valid UTF-8 according to PCRE */

/* array flags */
#define IS_ARRAY_IMMUTABLE			GC_IMMUTABLE
#define IS_ARRAY_PERSISTENT			GC_PERSISTENT

/* object flags (zval.value->gc.u.flags) */
#define IS_OBJ_WEAKLY_REFERENCED	GC_PERSISTENT
#define IS_OBJ_DESTRUCTOR_CALLED	(1<<8)
#define IS_OBJ_FREE_CALLED			(1<<9)

/* Recursion protection macros must be used only for arrays and objects */
#define GC_IS_RECURSIVE(p) \
	(GC_FLAGS(p) & GC_PROTECTED)
```






https://www.phpinternalsbook.com/php7/zvals/memory_management.html

zend_refcounted
    refcount
    type_info

文字列や配列は immutable になることがある
    refcount も変化しない
    opcache で共有されるメモリは必ず immutable
        複数のプロセスから参照される
    複数リクエストで使い回すデータは immutable
        並列アクセスされるため
            PHP の refcount は thread-safe でない

persistent allocator と per-request allocator
    persistent allocator
        malloc/free (pemalloc/pefree)
        PHP は関知しない
        リクエスト間で使い回される
        immutable であるか thread-local な領域に確保するかする必要がある
    per-request allocator
        Zend Memory Manager
        リクエストを処理し終わったら破棄される

zval は通常ヒープに確保されない
    ヒープに確保されたより大きなオブジェクトのフィールドとして確保されることはある
        zend_reference はそうだった気がする
    スタックに確保した zval はそのスコープで使い切らなければならない
        引数や返り値を通して by-value で受け渡してはならない
            スコープ外へとコピーされてしまうので

zval そのものは通常共有しない
    zval が内部的に持っている zend_string や zend_array などを共有する
        これらは refcounted

refcount が減ったときにそれがゼロでなければ、サイクルの一部かもしれない
    こういった参照をマークしておいて、GC の root にする

> the structure is marked as potentially circular (the GC_NOT_COLLECTABLE flag is not set)


```c
typedef struct _zend_refcounted zend_refcounted;

struct _zend_refcounted {
	zend_refcounted_h gc;
};

typedef struct _zend_refcounted_h {
	uint32_t         refcount;			/* reference counter 32-bit */
	union {
		uint32_t type_info;
	} u;
} zend_refcounted_h;
```



https://www.phpinternalsbook.com/php7/zvals/references.html



https://www.phpinternalsbook.com/php7/internal_types/strings/zend_strings.html

ハッシュのキャッシュ (h) を持っている
intern することができる
    opcache を使う場合、リクエスト処理の外で intern した文字列はプロセス間やスレッド間で共有される
    IS_STR_PERMANENT フラグが立てられる



https://www.phpinternalsbook.com/php7/extensions_design/php_lifecycle.html

GINIT
    global init
MINIT
    module init
RINIT
    request init
RSHUTDOWN
    request shutdown
RPSHUTDOWN
    request post shutdown
MSHUTDOWN
    module shutdown
GSHUTDOWN
    global shutdown

```
// 前とは名前が変わったらしい
#define PHP_MINIT_FUNCTION		ZEND_MODULE_STARTUP_D
#define PHP_MSHUTDOWN_FUNCTION	ZEND_MODULE_SHUTDOWN_D
#define PHP_RINIT_FUNCTION		ZEND_MODULE_ACTIVATE_D
#define PHP_RSHUTDOWN_FUNCTION	ZEND_MODULE_DEACTIVATE_D
#define PHP_MINFO_FUNCTION		ZEND_MODULE_INFO_D
#define PHP_GINIT_FUNCTION		ZEND_GINIT_FUNCTION
#define PHP_GSHUTDOWN_FUNCTION	ZEND_GSHUTDOWN_FUNCTION
// RPSHUTDOWN はなくなった?
```

CLI は一つのリクエストを処理して終了するという扱い



https://www.phpinternalsbook.com/php7/extensions_design/globals_management.html

リクエスト間で共有される状態をどう管理すればよいか?
    プロセスベースで並列処理をおこなうなら、グローバル変数を使えばよい
    スレッドベースで並列処理をおこなうなら、thread-local 領域に保存する
        コンパイルオプションに応じて自動で切り替えるマクロがある
    初期化したあと参照しかしないならグローバル変数でもよい



https://www.phpinternalsbook.com/php7/memory_management/zend_memory_manager.html

Zend Memory Manager (ZendMM; ZMM) は per-requset な allocator
    emalloc/efree
    memory_limit の監視もおこなう
        persistent allocator (malloc/free) によって管理される領域は関知しない

shared-nothing アーキテクチャ
    リクエスト間で共有されるデータが (ほとんど) ない

heap
chunk
map
page
bin
huge_list
    説明しない




zend_gc.c

* BLACK  (GC_BLACK)   - In use or free.
    GC の対象外
* GREY   (GC_GREY)    - Possible member of cycle.
    サイクルの可能性あり
* WHITE  (GC_WHITE)   - Member of garbage cycle.
    ゴミサイクルの一部
* PURPLE (GC_PURPLE)  - Possible root of cycle.
    サイクルの起点の可能性あり

gc_mark_roots からスタート
    possible roots を traverse
    purple なオブジェクトに対して DFS、mark_grey を呼び出していく

gc_scan_roots
    各 root に対して gc_scan を呼ぶ
        grey で refcount > 0
            gc_scan_black を呼ぶ
                解放できるかもしれないと思ったが、使用中なので解放できないことがわかった
                => 子も含めて black で塗り直す
        grey で refcount = 0
            white としてマーク (ゴミ確定)

A node MAY be added to possible roots when ZEND_UNSET_VAR happens or
zend_assign_to_variable is called only when possible garbage node is
produced.
gc_possible_root() will be called to add the nodes to possible roots.



zend_gc_collect_cycles
    gc_mark_roots
    gc_scan_roots
    gc_collect_roots
    gc_compact



gc_mark_roots
    すべての purple な roots に対して、
        grey にする
        gc_mark_grey を呼び出す

gc_mark_grey
    すべての子に対して、
        refounct を 1減らす
        grey でなければ grey で塗る

gc_scan_roots
    すべての grey な roots に対して、
        white にする
        gc_scan を呼び出す

gc_scan
    自身が white でなければ終わり
    refcount > 0 なら自身を black にして gc_scan_black を呼ぶ
    refcount = 0 なら子をチェックして、grey だったら white に

gc_scan_black
    refcount を 1増やして再帰的に black に

gc_collect_roots
    black な roots を root buffer から消す
    すべての white な roots に対して
        black にする
        gc_collect_white を呼ぶ

gc_collect_white
    自身を garbage とマーク
    すべての子に対して、
        refcount を 1増やす
        white なら black にする

gc_compact
    Two-finger compaction algorithm
    いわゆる「コンパクション」ではなく、GC の root objects を貯めておくバッファをコンパクションしている
    先頭に使っている root objects を持ってきて、後ろに未使用 objects を持っていく




zend_gc_collect_cycles が呼ばれるタイミング
    gc_possible_root_when_full から (だけ)
    あとは PHP レベルで gc_collect_cycles() を呼んだときだけ

gc_possible_root_when_full は gc_possible_root からだけ呼ばれる
    root buffer が一杯になったときだけ解放処理が走る

gc_possible_root が呼ばれるタイミング
    gc_check_possible_root
    gc_check_possible_root_no_ref
    zend_object_release
        refcount を減らしたときに 0 でなければ root かもしれない
        ただし GC_NOT_COLLECTABLE フラグが立っていないオブジェクトに限る
    Zend/zend_vm_def.h / Zend/zend_vm_execute.h / ext/opcache/jit/zend_jit_ir.c にもあるが追うのが難しそう
        assign したときの元の値に対して refcount を減らしてみて、0 でないなら root かもしれない
            実質的にやっていることは zend_object_release と同一に見える


* zend_refcounted を持っている構造体には何がある?
    * zend_string
    * zend_array
    * zend_object
    * zend_resource
    * zend_reference




弱参照
    #define IS_OBJ_WEAKLY_REFERENCED	GC_PERSISTENT
    Zend/zend_weakrefs.c
    グローバルに weakrefs という辞書を持っていて、object のポインタから WeakReference へ変換する
    zend_weakrefs_notify
        弱参照されているオブジェクト側にフラグを立てておいて、フラグが立っていれば dtor からこれを呼ぶ
        そのオブジェクトを参照していた weakref は無効になる




* GC 関係のフラグ
    * GC_NOT_COLLECTABLE
        * refcounted ではあるが、絶対に cycle の一部ではない
            * string
            * resource
            * reference
    * GC_PROTECTED
        * used for recursion detection
        * var_dump で再帰的な構造をダンプしたときとかに使われるらしい
        * GC_PROTECT_RECURSION
            * array_walk_recursive とかでも使われている
    * GC_IMMUTABLE
        * can't be changed in place
        * プロセス or スレッド間で共有 (opcache)
    * GC_PERSISTENT
        * allocated using malloc()
    * GC_PERSISTENT_LOCAL
        * persistent, but thread-local





一度 root buffer に入って、あとから (循環参照ではなかったために) 消えた場合どうなる?
    gc_remove_from_roots
    gc_remove_from_buffer
    GC_REMOVE_FROM_BUFFER
        使っている tmpvar は除外されるっぽい
    gc_remove_nested_data_from_buffer
    消す処理自体はあるが、これらを解放処理のたびに呼んでいるわけではなさそうな気がする
        再利用された場合は、少なくとも purple ではなくなるからいいのかな?
        gc_collect_roots で black な root は消される
            初期状態は black (数値としては 0) なはず




root buffer のサイズ、今の実装だと可変っぽい

```c
#define GC_DEFAULT_BUF_SIZE  (16 * 1024)        // 16k
#define GC_BUF_GROW_STEP     (128 * 1024)       // 128k
#define GC_MAX_UNCOMPRESSED  (512 * 1024)       // 512k
#define GC_MAX_BUF_SIZE      0x40000000         // 1024M
#define GC_THRESHOLD_DEFAULT (10000 + GC_FIRST_ROOT)
#define GC_THRESHOLD_STEP    10000
#define GC_THRESHOLD_MAX     1000000000
#define GC_THRESHOLD_TRIGGER 100

gc_adjust_threshold
/* TODO Very simple heuristic for dynamic GC buffer resizing:
 * If there are "too few" collections, increase the collection threshold
 * by a fixed step */
```

あまり回収されなかったら閾値を上げる、回収量が大ければ閾値を下げる









# プロポーザル

## PHP 処理系の garbage collection を理解する 〜メモリはいつ解放されるのか〜

Garbage collection (GC) とは、確保したメモリ領域を適切なタイミングで解放する仕組みのことです。
メモリが比較的潤沢になった今でも、メモリ溢れによるサーバ障害は決して珍しくありません。
PHP における GC を理解し、メモリを意識したプログラミングをすることで、本番サーバを夜中に落とさないようにしましょう。

### 主な対象

* 普段メモリを意識してプログラミングしていないかた
* 言語処理系の内部実装に興味があるかた
* メモリ溢れで本番環境をダウンさせたことのあるかた

### 話すこと

* PHP における GC のアルゴリズム
* 確保したメモリはいつ解放されるのか
* メモリ溢れを起こさないプログラミング

### 話さないこと

* GC のパラメータをいじってチューニングする






# 構成

* 前提知識
    * メモリとは
        * 知っている前提で軽く流す
    * メモリ管理
* なぜメモリ管理が重要なのか
    * メモリが潤沢な時代でも...
        * メモリは無限ではない
        * 速度の話は要る?
    * メモリについて何も考えていないと起こること
        * スラッシング
* メモリ管理
    * 確保と解放
    * GC のモチベーション
        * メモリリーク・セグフォ
* PHP の GC
    * 一言で
    * 参照カウント
    * 循環参照
    * Mark & sweep
    * 具体的な型では?
    * CoW
    * イミュータブル・共有
    * リクエストバウンドかそれ以外か
    * memory_limit
    * Zend Memory Manager
    * 解放のトリガー
    * デストラクタ
    * 参照
    * fclose()
    * GC を制御するパラメータの意味
    * 弱参照
* メモリを溢れさせないために
    * 載らない量を載せない
    * 一度に扱う量を減らす
* まとめ
* Q&A