diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-02-04 01:20:34 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-02-22 07:11:04 +0900 |
| commit | 6f93cab592ddf3b8e7c544bae489ebef63a091cc (patch) | |
| tree | 9aa7dcf31d01c6dfda5bbd1c91e31f388649df0e | |
| parent | d1f630f6ba442c9f01a58bf56a06312be6c50166 (diff) | |
| download | phpcon-nagoya-2025-slides-6f93cab592ddf3b8e7c544bae489ebef63a091cc.tar.gz phpcon-nagoya-2025-slides-6f93cab592ddf3b8e7c544bae489ebef63a091cc.tar.zst phpcon-nagoya-2025-slides-6f93cab592ddf3b8e7c544bae489ebef63a091cc.zip | |
add slides
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | Dockerfile | 18 | ||||
| -rw-r--r-- | Makefile | 40 | ||||
| -rw-r--r-- | NOTE.md | 546 | ||||
| -rw-r--r-- | assets/cycle_1.png | bin | 0 -> 16535 bytes | |||
| -rw-r--r-- | assets/cycle_2.png | bin | 0 -> 12542 bytes | |||
| -rw-r--r-- | assets/me_1200x1200.png | bin | 0 -> 93805 bytes | |||
| -rw-r--r-- | assets/ms_1.png | bin | 0 -> 7781 bytes | |||
| -rw-r--r-- | assets/ms_2.png | bin | 0 -> 7806 bytes | |||
| -rw-r--r-- | assets/ms_3.png | bin | 0 -> 7876 bytes | |||
| -rw-r--r-- | assets/ms_4.png | bin | 0 -> 7876 bytes | |||
| -rw-r--r-- | assets/ms_5.png | bin | 0 -> 9904 bytes | |||
| -rw-r--r-- | assets/msphp_1.png | bin | 0 -> 9463 bytes | |||
| -rw-r--r-- | assets/msphp_2.png | bin | 0 -> 12314 bytes | |||
| -rw-r--r-- | assets/msphp_3.png | bin | 0 -> 12631 bytes | |||
| -rw-r--r-- | assets/msphp_4.png | bin | 0 -> 12074 bytes | |||
| -rw-r--r-- | assets/msphp_5.png | bin | 0 -> 14495 bytes | |||
| -rw-r--r-- | assets/rc_1.png | bin | 0 -> 13145 bytes | |||
| -rw-r--r-- | assets/rc_10.png | bin | 0 -> 10428 bytes | |||
| -rw-r--r-- | assets/rc_11.png | bin | 0 -> 2350 bytes | |||
| -rw-r--r-- | assets/rc_2.png | bin | 0 -> 14371 bytes | |||
| -rw-r--r-- | assets/rc_3.png | bin | 0 -> 22707 bytes | |||
| -rw-r--r-- | assets/rc_4.png | bin | 0 -> 23784 bytes | |||
| -rw-r--r-- | assets/rc_5.png | bin | 0 -> 26540 bytes | |||
| -rw-r--r-- | assets/rc_6.png | bin | 0 -> 26753 bytes | |||
| -rw-r--r-- | assets/rc_7.png | bin | 0 -> 25842 bytes | |||
| -rw-r--r-- | assets/rc_8.png | bin | 0 -> 26739 bytes | |||
| -rw-r--r-- | assets/rc_9.png | bin | 0 -> 23511 bytes | |||
| -rw-r--r-- | patches/figbox.satyh.patch | 19 | ||||
| -rw-r--r-- | slides.md | 216 | ||||
| -rw-r--r-- | slides.pdf | bin | 0 -> 494910 bytes | |||
| -rw-r--r-- | slides.saty | 789 | ||||
| -rw-r--r-- | slydifi/LICENSE | 11 | ||||
| -rw-r--r-- | slydifi/my-theme.satyh | 426 |
34 files changed, 2067 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e7b283b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.satysfi +/slides.satysfi-aux diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..38d7e0a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM amutake/satysfi + +RUN opam update && \ + opam install satysfi-base + +RUN opam install satysfi-class-slydifi && \ + opam install satysfi-code-printer && \ + opam install satysfi-fonts-noto-sans && \ + opam install satysfi-fonts-noto-sans-cjk-jp + +RUN eval $(opam env) && \ + satyrographos install --copy + +WORKDIR /work + +COPY patches/figbox.satyh.patch /root/.satysfi/dist/packages/figbox/figbox.satyh.patch +RUN cd /root/.satysfi/dist/packages/figbox && \ + patch -p0 < figbox.satyh.patch diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4345fa0 --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ +# SATYSFI_BIN := satysfi +SATYSFI_BIN := ../fork/SATySFi/result/bin/satysfi + +.PHONY: all +all: build + +# Build slide PDF. +.PHONY: build +build: slides.pdf + +slides.pdf: slides.saty slydifi/my-theme.satyh + $(SATYSFI_BIN) slides.saty + +# Enter Docker shell. +.PHONY: shell +shell: + docker run \ + -it \ + --rm \ + --name satysfi \ + satysfi \ + sh + +# Build Docker container. +.PHONY: docker +docker: + docker build --tag satysfi . + +# Install dependencies. +.PHONY: deps +deps: + rm -rf .satysfi + docker create --name satysfi-tmp satysfi + docker cp -L satysfi-tmp:/root/.satysfi .satysfi + docker rm satysfi-tmp + +# Clean all artifacts. +.PHONY: clean +clean: + rm -f *.pdf *.satysfi-aux @@ -0,0 +1,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 diff --git a/assets/cycle_1.png b/assets/cycle_1.png Binary files differnew file mode 100644 index 0000000..1aa118c --- /dev/null +++ b/assets/cycle_1.png diff --git a/assets/cycle_2.png b/assets/cycle_2.png Binary files differnew file mode 100644 index 0000000..2cd61c1 --- /dev/null +++ b/assets/cycle_2.png diff --git a/assets/me_1200x1200.png b/assets/me_1200x1200.png Binary files differnew file mode 100644 index 0000000..fb3a7bf --- /dev/null +++ b/assets/me_1200x1200.png diff --git a/assets/ms_1.png b/assets/ms_1.png Binary files differnew file mode 100644 index 0000000..8e34393 --- /dev/null +++ b/assets/ms_1.png diff --git a/assets/ms_2.png b/assets/ms_2.png Binary files differnew file mode 100644 index 0000000..f93c9d8 --- /dev/null +++ b/assets/ms_2.png diff --git a/assets/ms_3.png b/assets/ms_3.png Binary files differnew file mode 100644 index 0000000..fdfca37 --- /dev/null +++ b/assets/ms_3.png diff --git a/assets/ms_4.png b/assets/ms_4.png Binary files differnew file mode 100644 index 0000000..76557e2 --- /dev/null +++ b/assets/ms_4.png diff --git a/assets/ms_5.png b/assets/ms_5.png Binary files differnew file mode 100644 index 0000000..7df3065 --- /dev/null +++ b/assets/ms_5.png diff --git a/assets/msphp_1.png b/assets/msphp_1.png Binary files differnew file mode 100644 index 0000000..4c49a8c --- /dev/null +++ b/assets/msphp_1.png diff --git a/assets/msphp_2.png b/assets/msphp_2.png Binary files differnew file mode 100644 index 0000000..fff9870 --- /dev/null +++ b/assets/msphp_2.png diff --git a/assets/msphp_3.png b/assets/msphp_3.png Binary files differnew file mode 100644 index 0000000..6eefd3a --- /dev/null +++ b/assets/msphp_3.png diff --git a/assets/msphp_4.png b/assets/msphp_4.png Binary files differnew file mode 100644 index 0000000..8cc5c1b --- /dev/null +++ b/assets/msphp_4.png diff --git a/assets/msphp_5.png b/assets/msphp_5.png Binary files differnew file mode 100644 index 0000000..2477c6f --- /dev/null +++ b/assets/msphp_5.png diff --git a/assets/rc_1.png b/assets/rc_1.png Binary files differnew file mode 100644 index 0000000..4d2cce4 --- /dev/null +++ b/assets/rc_1.png diff --git a/assets/rc_10.png b/assets/rc_10.png Binary files differnew file mode 100644 index 0000000..4e8da67 --- /dev/null +++ b/assets/rc_10.png diff --git a/assets/rc_11.png b/assets/rc_11.png Binary files differnew file mode 100644 index 0000000..3426f00 --- /dev/null +++ b/assets/rc_11.png diff --git a/assets/rc_2.png b/assets/rc_2.png Binary files differnew file mode 100644 index 0000000..6f8a97e --- /dev/null +++ b/assets/rc_2.png diff --git a/assets/rc_3.png b/assets/rc_3.png Binary files differnew file mode 100644 index 0000000..b0a51da --- /dev/null +++ b/assets/rc_3.png diff --git a/assets/rc_4.png b/assets/rc_4.png Binary files differnew file mode 100644 index 0000000..7b4be66 --- /dev/null +++ b/assets/rc_4.png diff --git a/assets/rc_5.png b/assets/rc_5.png Binary files differnew file mode 100644 index 0000000..fd08db4 --- /dev/null +++ b/assets/rc_5.png diff --git a/assets/rc_6.png b/assets/rc_6.png Binary files differnew file mode 100644 index 0000000..4a46bd9 --- /dev/null +++ b/assets/rc_6.png diff --git a/assets/rc_7.png b/assets/rc_7.png Binary files differnew file mode 100644 index 0000000..6b073a2 --- /dev/null +++ b/assets/rc_7.png diff --git a/assets/rc_8.png b/assets/rc_8.png Binary files differnew file mode 100644 index 0000000..643be40 --- /dev/null +++ b/assets/rc_8.png diff --git a/assets/rc_9.png b/assets/rc_9.png Binary files differnew file mode 100644 index 0000000..8a9211c --- /dev/null +++ b/assets/rc_9.png diff --git a/patches/figbox.satyh.patch b/patches/figbox.satyh.patch new file mode 100644 index 0000000..5226b06 --- /dev/null +++ b/patches/figbox.satyh.patch @@ -0,0 +1,19 @@ +--- figbox.satyh 2025-01-19 23:30:37.843791596 +0900 ++++ figbox.satyh 2025-01-19 23:31:17.251295982 +0900 +@@ -144,15 +144,7 @@ + let (_, ext) = Option.from (0, ` `) (splitlst |> List.nth (List.length splitlst - 1)) in + match ext with + | `pdf` -> use-image-by-width (load-pdf-image pth pg) wid +- | `jpg` -> use-image-by-width (load-image pth) wid +- | `jpeg` -> use-image-by-width (load-image pth) wid +- | `jpe` -> use-image-by-width (load-image pth) wid +- | `jfif` -> use-image-by-width (load-image pth) wid +- | `jfi` -> use-image-by-width (load-image pth) wid +- | `jif` -> use-image-by-width (load-image pth) wid +- | _ -> +- let () = display-message (`Failed to include image: [` ^ pth ^ `]`) in +- inline-nil ++ | _ -> use-image-by-width (load-image pth) wid + in + Box(embedf) + diff --git a/slides.md b/slides.md new file mode 100644 index 0000000..74c56c6 --- /dev/null +++ b/slides.md @@ -0,0 +1,216 @@ +# PHP 処理系の garbage collection を理解する +メモリはいつ解放されるのか + + + + + + +はじめに + +メモリ + +コンピュータの一時データ置き場 + + +メモリの容量は増え続けている + +無限ではない + +使い果たすと... +プロセスの強制終了 +極端なパフォーマンスの低下 + + + +メモリ管理 + +必要なメモリを確保する +不要なメモリを解放する + + +メモリの「確保」割り当て + +このサイズのメモリが必要です + + +メモリの「解放」 + +ここのメモリは不要になりました + + + +メモリ管理は難しい + + +メモリリーク + 解放すべきメモリ領域を解放していない +double free + 解放済みのメモリ領域をまた解放した +use-after-free + 解放済みのメモリ領域にアクセスした +buffer overflow + 確保したメモリ領域を越えてアクセスした + + + +PHP でメモリ管理を直接おこなう必要はない + + + +Garbage Collection (GC) + +メモリ管理の自動化 +自動でメモリを解放 + + + +PHP の GC + + + +参照カウント + マークアンドスイープ + + + + +参照カウント + +参照されている数を数える +ゼロになったら解放する + + + +$a = ["foo"]; +$b = ["bar"]; +$a[] = $b; +unset($b); +unset($a); + + + +参照カウントの利点 + +未使用オブジェクトが即座に解放される +解放にかかるコストが全体に分散する + + +参照カウントの欠点 + +参照の追加にコストがかかる +マルチスレッド環境に向かない +循環参照を扱えない + + + +$a = new stdClass(); +$b = new stdClass(); +$a->x = $b; +$b->x = $a; + + + +$a = new stdClass(); +$b = new stdClass(); +$a->x = $b; +$b->x = $a; +unset($b); +unset($a); + + + + +参照カウントでは循環参照を解放できない + + + +マークアンドスイープ (mark & sweep) + +循環参照の解放のみ担う + + +最も基本的なマークアンドスイープ +PHP のマークアンドスイープ + + + +マークフェーズ +スイープフェーズ + + + +マーク + 確実に使われているオブジェクト (ルート) に印を付ける + そこから辿れるオブジェクトに印を付ける +スイープ + 全オブジェクトを調べて、印が付いていなければ解放する + + + + +図 + + + +PHP のマークアンドスイープ + + +ほとんどのオブジェクトは参照カウントで解放される +全オブジェクトを走査しなくていい + + +循環参照「かもしれない」オブジェクトを登録 + +refcount を減らしたときに 0 にならなかった + + + + +マーク + 循環参照かもしれないオブジェクトを root buffer へ登録 + root buffer から辿れるオブジェクトの refcount を 1減らす +スイープ + すべての root buffer から辿れるオブジェクトについて、 + refcount = 0 なら解放する + refcount > 0 なら refcount を1増やして root buffer から消す + + + +図 + + + +PHP のマークアンドスイープ GC が呼ばれるタイミング + +root buffer が一杯になったとき (デフォルトで 10,000) + + + + +PHP の GC + +参照カウント + マークアンドスイープ + +循環参照以外は参照カウントで、 +循環参照はマークアンドスイープで解放 + + + +多くのオブジェクトは未使用になると即座に解放される +循環参照を形成しているオブジェクトは遅れて解放される + +fclose() は明示的に呼ぶべきか? +常に呼ぶべき +呼ばなくても問題ないかを判定するのが困難 +呼ばなくても問題ない状態を維持できるとは限らない + + + + +話せなかったこと + +複数スレッド/プロセス間での共有 +リクエスト毎に確保・解放されるメモリとグローバルなメモリ +Copy on Write +弱参照 +memory_limit diff --git a/slides.pdf b/slides.pdf Binary files differnew file mode 100644 index 0000000..36c61a3 --- /dev/null +++ b/slides.pdf diff --git a/slides.saty b/slides.saty new file mode 100644 index 0000000..6a2ba7c --- /dev/null +++ b/slides.saty @@ -0,0 +1,789 @@ +@import: ./slydifi/my-theme +@require: code-printer/code-design +@require: code-printer/code-printer +@require: code-printer/code-syntax +@require: code-printer/code-theme +@require: figbox/figbox + +let ex-big-textbox ?:size-opt it = + let size = Option.from 48pt size-opt in + FigBox.textbox?:(set-font-size size) it + +let big-textbox ?:size-opt it = + let size = Option.from 32pt size-opt in + FigBox.textbox?:(set-font-size size) it + +let mid-textbox ?:size-opt it = + let size = Option.from 24pt size-opt in + FigBox.textbox?:(set-font-size size) it + +let with-frame figbox = figbox + |> FigBox.hvmargin 16pt + |> FigBox.frame 2pt Color.black + +let-block +code-block-php source = + '< + +code-printer?:( + CodePrinter.make-config CodeSyntax.php CodeTheme.iceberg-light + |> CodePrinter.set-number-fun CodeDesign.number-fun-null + |> CodePrinter.set-basic-font-size 32pt + )(source); + > + +open FigBox +in + +document '< + +make-title(| + title = { + |PHP処理系の + |garbage collection を理解する + |~メモリはいつ解放されるのか~ + |}; + author = {|nsfisis (いまむら)|}; + date = {|PHPカンファレンス名古屋2025|}; + |); + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +frame{自己紹介}< + +fig-center(vconcat [ + gap 30pt; + big-textbox{いまむら}; + ]); + +fig-center(vconcat [ + ex-big-textbox{nsfisis}; + ]); + +fig-center(vconcat [ + include-image 128pt `assets/me_1200x1200.png`; + ]); + +fig-center(vconcat [ + big-textbox{\@ デジタルサーカス株式会社}; + ]); + > + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +frame{はじめに}< + +p{ + メモリ + } + +p{ + コンピュータの一時データ置き場 + } + > + + +frame{はじめに}< + +p{ + メモリの容量は増え続けている + } + > + + +frame{はじめに}< + +p{ + メモリの容量は増え続けている + } + +p{} + +p{ + 無限ではない + } + > + + +frame{はじめに}< + +p{ + 使い果たすと...... + } + > + + +frame{はじめに}< + +p{ + 使い果たすと...... + } + +p{} + +listing{ + * プロセスの強制終了 + * 極端なパフォーマンスの低下 + } + > + + +frame{メモリ管理}< + +p{ + メモリ管理 + } + > + + +frame{メモリ管理}< + +p{ + メモリ管理 + } + +p{} + +listing{ + * 必要なメモリを確保する + * 不要なメモリを解放する + } + > + + +frame{メモリ管理}< + +p{ + メモリの「確保」(割り当て) + } + +p{} + +p{ + 必要なメモリのサイズを要求する + } + > + + +frame{メモリ管理}< + +p{ + メモリの「解放」 + } + +p{} + +p{ + 不要になったメモリを再び利用可能にする + } + > + + +frame{メモリ管理}< + +p{ + メモリ管理は難しい + } + > + + +frame{メモリ管理}< + +p{ + メモリ管理は難しい + } + +p{} + +listing{ + * Memory leak + * Double free + * Use-after-free + * Buffer overflow + } + > + + +frame{メモリ管理}< + +p{ + PHP でメモリ管理を直接おこなう必要はない + } + > + + +frame{メモリ管理}< + +p{ + PHP でメモリ管理を直接おこなう必要はない + } + +p{} + +p{ + Garbage Collection + } + > + + +frame{Garbage Collection}< + +p{ + Garbage Collection (GC) + } + > + + +frame{Garbage Collection}< + +p{ + Garbage Collection (GC) + } + +p{} + +p{ + メモリ管理の自動化 + } + +p{ + 自動でメモリを解放 + } + > + + +frame{PHP の GC 概要}< + +p{ + PHP の GC + } + > + + +frame{PHP の GC 概要}< + +p{ + PHP の GC + } + +p{} + +p{ + 参照カウント + マークアンドスイープ + } + > + + +frame{参照カウント}< + +p{ + 参照カウント + } + > + + +frame{参照カウント}< + +p{ + 参照カウント + } + +p{} + +p{ + 参照されている数を数える + } + +p{ + ゼロになったら解放する + } + > + + +frame{参照カウント}< + +code-block-php(`$a = ["foo"]; +$b = ["bar"]; +$a[] = $b; +unset($b); +unset($a); + +`#); + > + + +frame{参照カウント}< + +code-block-php(`$a = ["foo"]; +$b = ["bar"]; +$a[] = $b; +unset($b); +unset($a); + +`#); + +fig-abs-pos((127mm, 40mm))(include-image 127mm `assets/rc_1.png`); + > + + +frame{参照カウント}< + +code-block-php(`$a = ["foo"]; +$b = ["bar"]; +$a[] = $b; +unset($b); +unset($a); + +`#); + +fig-abs-pos((127mm, 40mm))(include-image 127mm `assets/rc_2.png`); + > + + +frame{参照カウント}< + +code-block-php(`$a = ["foo"]; +$b = ["bar"]; +$a[] = $b; +unset($b); +unset($a); + +`#); + +fig-abs-pos((127mm, 40mm))(include-image 127mm `assets/rc_3.png`); + > + + +frame{参照カウント}< + +code-block-php(`$a = ["foo"]; +$b = ["bar"]; +$a[] = $b; +unset($b); +unset($a); + +`#); + +fig-abs-pos((127mm, 40mm))(include-image 127mm `assets/rc_4.png`); + > + + +frame{参照カウント}< + +code-block-php(`$a = ["foo"]; +$b = ["bar"]; +$a[] = $b; +unset($b); +unset($a); + +`#); + +fig-abs-pos((127mm, 40mm))(include-image 127mm `assets/rc_5.png`); + > + + +frame{参照カウント}< + +code-block-php(`$a = ["foo"]; +$b = ["bar"]; +$a[] = $b; +unset($b); +unset($a); + +`#); + +fig-abs-pos((127mm, 40mm))(include-image 127mm `assets/rc_6.png`); + > + + +frame{参照カウント}< + +code-block-php(`$a = ["foo"]; +$b = ["bar"]; +$a[] = $b; +unset($b); +unset($a); + +`#); + +fig-abs-pos((127mm, 40mm))(include-image 127mm `assets/rc_7.png`); + > + + +frame{参照カウント}< + +code-block-php(`$a = ["foo"]; +$b = ["bar"]; +$a[] = $b; +unset($b); +unset($a); + +`#); + +fig-abs-pos((127mm, 40mm))(include-image 127mm `assets/rc_8.png`); + > + + +frame{参照カウント}< + +code-block-php(`$a = ["foo"]; +$b = ["bar"]; +$a[] = $b; +unset($b); +unset($a); + +`#); + +fig-abs-pos((127mm, 40mm))(include-image 127mm `assets/rc_9.png`); + > + + +frame{参照カウント}< + +code-block-php(`$a = ["foo"]; +$b = ["bar"]; +$a[] = $b; +unset($b); +unset($a); + +`#); + +fig-abs-pos((127mm, 40mm))(include-image 127mm `assets/rc_10.png`); + > + + +frame{参照カウント}< + +code-block-php(`$a = ["foo"]; +$b = ["bar"]; +$a[] = $b; +unset($b); +unset($a); + +`#); + +fig-abs-pos((127mm, 40mm))(include-image 127mm `assets/rc_11.png`); + > + + +frame{参照カウント}< + +p{ + 参照カウントの利点 + } + +p{} + +listing{ + * 未使用オブジェクトが即座に解放される + * 解放にかかるコストが全体に分散する + } + > + + +frame{参照カウント}< + +p{ + 参照カウントの欠点 + } + +p{} + +listing{ + * 参照の追加にコストがかかる + * マルチスレッド/プロセス環境に向かない + * 循環参照を扱えない + } + > + + +frame{参照カウント}< + +code-block-php(`$a = new stdClass(); +$b = new stdClass(); +$a->x = $b; +$b->x = $a; + +`#); + > + + +frame{参照カウント}< + +code-block-php(`$a = new stdClass(); +$b = new stdClass(); +$a->x = $b; +$b->x = $a; + +`#); + +fig-abs-pos((127mm, 40mm))(include-image 127mm `assets/cycle_1.png`); + > + + +frame{参照カウント}< + +code-block-php(`$a = new stdClass(); +$b = new stdClass(); +$a->x = $b; +$b->x = $a; +unset($b); +unset($a); + +`#); + +fig-abs-pos((127mm, 40mm))(include-image 127mm `assets/cycle_1.png`); + > + + +frame{参照カウント}< + +code-block-php(`$a = new stdClass(); +$b = new stdClass(); +$a->x = $b; +$b->x = $a; +unset($b); +unset($a); + +`#); + +fig-abs-pos((127mm, 40mm))(include-image 127mm `assets/cycle_2.png`); + > + + +frame{参照カウント}< + +p{ + 参照カウントでは循環参照を解放できない + } + > + + +frame{マークアンドスイープ}< + +p{ + マークアンドスイープ (mark & sweep) + } + > + + +frame{マークアンドスイープ}< + +p{ + マークアンドスイープ (mark & sweep) + } + +p{} + +p{ + PHP では循環参照の解放のみ担う + } + > + + +frame{マークアンドスイープ}< + +p{ + マークアンドスイープ (mark & sweep) + } + +p{} + +listing{ + * 基本的なマークアンドスイープ + * PHP でのマークアンドスイープ + } + > + + +frame{マークアンドスイープ}< + +p{ + マークアンドスイープの流れ + } + +p{} + +listing{ + * マークフェーズ + * スイープフェーズ + } + > + + +frame{マークアンドスイープ}< + +p{ + マークフェーズ + } + +p{} + +listing{ + * 確実に使われているオブジェクト (ルート) に印を付ける + * そこから辿れるオブジェクトに印を付ける + } + > + + +frame{マークアンドスイープ}< + +p{ + マークフェーズ + } + +fig-abs-pos((60mm, 20mm))(include-image 80mm `assets/ms_1.png`); + > + + +frame{マークアンドスイープ}< + +p{ + マークフェーズ + } + +fig-abs-pos((60mm, 20mm))(include-image 80mm `assets/ms_2.png`); + > + + +frame{マークアンドスイープ}< + +p{ + マークフェーズ + } + +fig-abs-pos((60mm, 20mm))(include-image 80mm `assets/ms_3.png`); + > + + +frame{マークアンドスイープ}< + +p{ + スイープフェーズ + } + +p{} + +listing{ + * 全オブジェクトを調べて、印が付いていなければ解放する + } + > + + +frame{マークアンドスイープ}< + +p{ + スイープフェーズ + } + +fig-abs-pos((60mm, 20mm))(include-image 80mm `assets/ms_4.png`); + > + + +frame{マークアンドスイープ}< + +p{ + スイープフェーズ + } + +fig-abs-pos((60mm, 20mm))(include-image 80mm `assets/ms_5.png`); + > + + +frame{マークアンドスイープ}< + +p{ + PHP でのマークアンドスイープ + } + > + + +frame{マークアンドスイープ}< + +p{ + PHP でのマークアンドスイープ + } + +p{} + +p{ + ほとんどのオブジェクトは参照カウントで解放される + } + +p{ + 全オブジェクトを走査しなくていい + } + > + + +frame{マークアンドスイープ}< + +p{ + PHP でのマークアンドスイープ + } + +p{} + +p{ + 循環参照「かもしれない」オブジェクトを登録しておく + } + +p{ + そのオブジェクトとそこから辿れるオブジェクトだけ走査する + } + > + + +frame{マークアンドスイープ}< + +p{ + PHP でのマークアンドスイープ + } + +p{} + +p{ + 循環参照「かもしれない」オブジェクト + } + +p{} + +p{ + refcount を減らしたときに 0 にならなかったもの + } + +p{ + 循環参照は、間接的に自分で自分を指している + } + > + + +frame{マークアンドスイープ}< + +p{ + マークフェーズ + } + +p{} + +listing{ + * 循環参照かもしれないオブジェクトをルートバッファへ登録する + * ルートバッファから辿れる全オブジェクトの refcount を 1減らす + } + > + + +frame{マークアンドスイープ}< + +p{ + マークフェーズ + } + +fig-abs-pos((60mm, 20mm))(include-image 80mm `assets/msphp_1.png`); + > + + +frame{マークアンドスイープ}< + +p{ + マークフェーズ + } + +fig-abs-pos((60mm, 20mm))(include-image 80mm `assets/msphp_2.png`); + > + + +frame{マークアンドスイープ}< + +p{ + スイープフェーズ + } + +p{} + +listing{ + * すべてのルートバッファから辿れるオブジェクトについて、 + ** refcount が 0 でないなら1増やす + ** refcount が 0 なら解放する + * 処理後はルートバッファから取り除く + } + > + + +frame{マークアンドスイープ}< + +p{ + スイープフェーズ + } + +fig-abs-pos((60mm, 20mm))(include-image 80mm `assets/msphp_3.png`); + > + + +frame{マークアンドスイープ}< + +p{ + スイープフェーズ + } + +fig-abs-pos((60mm, 20mm))(include-image 80mm `assets/msphp_4.png`); + > + + +frame{マークアンドスイープ}< + +p{ + スイープフェーズ + } + +fig-abs-pos((60mm, 20mm))(include-image 80mm `assets/msphp_5.png`); + > + + +frame{マークアンドスイープ}< + +p{ + マークアンドスイープが走るタイミング + } + +p{} + +listing{ + * \code(`gc_collect_cycles()`); を呼んだとき + * ルートバッファが一杯になったとき + ** デフォルトは 10,000 + } + > + + +frame{マークアンドスイープ}< + +p{ + マークアンドスイープの利点 + } + +p{} + +listing{ + * 循環参照を解放できる + * GC が動いていないときのオーバーヘッドがない + } + > + + +frame{マークアンドスイープ}< + +p{ + マークアンドスイープの欠点 + } + +p{} + +listing{ + * GC に時間がかかる + } + > + + +frame{振り返り}< + +p{ + PHP の GC + } + +p{} + +p{ + 参照カウント + マークアンドスイープ + } + > + + +frame{振り返り}< + +p{ + PHP の GC + } + +p{} + +p{ + 参照カウント + マークアンドスイープ + } + +p{} + +p{ + 循環参照以外は参照カウントで、 + } + +p{ + 循環参照はマークアンドスイープで解放 + } + > + + +frame{振り返り}< + +p{ + PHP の GC + } + +p{} + +p{ + 参照カウント + マークアンドスイープ + } + +p{} + +p{ + 多くの (循環参照でない) オブジェクトは未使用になると即座に解放される + } + +p{ + 循環参照を形成しているオブジェクトは遅れて解放される + } + > + + +frame{外部リソースの解放}< + +p{ + \code(`fclose()`); は明示的に呼ぶべきか? + } + > + + +frame{外部リソースの解放}< + +p{ + \code(`fclose()`); は明示的に呼ぶべきか? + } + +p{} + +p{ + 明示的に呼ばなくても、GC で解放されたタイミングでクローズされる + } + > + + +frame{外部リソースの解放}< + +p{ + \code(`fclose()`); は明示的に呼ぶべきか? + } + +p{} + +p{ + 常に呼ぶべき + } + > + + +frame{外部リソースの解放}< + +p{ + \code(`fclose()`); は明示的に呼ぶべきか? + } + +p{} + +p{ + 常に呼ぶべき + } + +listing{ + * 呼ばなくても問題ないかを判定するのが困難 + * 呼ばなくても問題ない状態を維持できるとは限らない + } + > + + +frame{外部リソースの解放}< + +p{ + \code(`fclose()`); は明示的に呼ぶべきか? + } + +p{} + +p{ + 常に呼ぶべき + } + +listing{ + * 呼ばなくても問題ないかを判定するのが困難 + * 呼ばなくても問題ない状態を維持できるとは限らない + } + +p{} + +p{ + 循環参照になっていないか、解放が遅れても問題ないなら呼ばなくてよい + } + > + + +frame{最後に}< + +p{ + 話せなかったこと + } + +p{} + +listing{ + * 複数スレッド/プロセス間での共有 + * リクエスト毎に確保・解放されるメモリとグローバルなメモリ + * Copy on Write + * 弱参照 + * \code(`memory_limit`); + } + > + +> diff --git a/slydifi/LICENSE b/slydifi/LICENSE new file mode 100644 index 0000000..356f486 --- /dev/null +++ b/slydifi/LICENSE @@ -0,0 +1,11 @@ +./my-theme.satyh is based on SLyDIFi's akasaka.satyh, which is licensed uner The MIT License. The original copyright notice is: + + + +Copyright (c) 2019 Mogami Shinichi + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/slydifi/my-theme.satyh b/slydifi/my-theme.satyh new file mode 100644 index 0000000..32ddb57 --- /dev/null +++ b/slydifi/my-theme.satyh @@ -0,0 +1,426 @@ +%% my-theme.satyh +%% +%% SLyDIFi theme file. + +@require: gr +@require: annot +@require: base/color-ext +@require: base/int +@require: class-slydifi/slydifi +@require: class-slydifi/footnote +@require: enumitem/enumitem + +type my-theme-config = (| + font-normal : context -> context; + font-bold : context -> context; + font-code : context -> context; + font-slide-title : context -> context; + font-slide-author : context -> context; + font-slide-date : context -> context; + font-section-title : context -> context; + font-frame-title : context -> context; + font-pagenum : context -> context; + font-footnote : context -> context; + + color-bg : color; + color-fg : color; + color-emph : color; + color-link : color; + color-title : color; + color-separator : color; + color-section-ribbon : color; + color-section-ribbon-intersect : color; + + length-frame-title-height : length; + length-frame-title-left-margin : length; + length-frame-title-bot-margin : length; + length-frame-body-top-margin : length; +|) + + +module SlydifiMyTheme: sig + + val layout: frame-layout + val document: block-text -> document + +% config + val config: my-theme-config SlydifiParam.t + val default-config: my-theme-config + direct +set-config : [my-theme-config] block-cmd + direct \set-config : [my-theme-config] inline-cmd + direct +with-config : [my-theme-config; block-text] block-cmd + direct \with-config : [my-theme-config; inline-text] inline-cmd + +% frames + %% スライドマスター。 + val frame-master: my-theme-config -> unit Frame.frame + %% ページ番号の付いたスライドマスター。 + val frame-master-with-footer: my-theme-config -> unit Frame.frame + %% スライド全体のタイトルフレーム。 + %% title: スライドのタイトル + %% title: スライドのタイトル + val frame-slide-title: my-theme-config -> + ((| title: inline-text list; author: inline-text list; date: inline-text list|)) + Frame.frame + %% セクションフレーム。 + % val frame-section-title: layout-slydifi-theme ((| title: inline-text list; |)) Frame.frame + %% タイトルフレーム。 + val frame-normal: my-theme-config -> ((| title: inline-text; inner: block-text |)) Frame.frame + +% frame commands + %% frame-slide-title に従ってタイトルスライドを生成する。 + direct +make-title : [(| title: inline-text list; author: inline-text list; date: inline-text list|);] block-cmd + %% frame-normal に従って通常のスライドを生成する。 + direct +section : [inline-text list; block-text;] block-cmd + direct +frame : [int?; inline-text; block-text;] block-cmd + +% inline commands + direct \emph : [(int -> bool)?; inline-text] inline-cmd + direct \link : [inline-text?; string] inline-cmd + direct \code : [string] inline-cmd + +% listing + direct +oitem : [(int -> bool); inline-text; block-text] block-cmd + +% 脚注 + direct \footnote : [inline-text] inline-cmd + direct \footnotetext : [string; inline-text] inline-cmd + direct \footnotemark : [string] inline-cmd + +end = struct + + let page-num = SlydifiParam.make 0 + + let layout = (| + paper-width = 254mm; + paper-height = 142.9mm; + text-width = 254mm -' 40pt; + text-height = 142.9mm -' 20pt; + text-horizontal-margin = 20pt; + text-vertical-margin = 10pt; + |) + let xrel xfloat = layout#paper-width *' xfloat + let yrel yfloat = layout#paper-height *' yfloat + let rel xfloat yfloat = (xrel xfloat, yrel yfloat) + +% configuration + let default-config = + let set-font-metrics fsize ctx = + ctx |> set-font-size fsize + |> set-paragraph-margin (fsize *' 0.6) (fsize *' 0.6) + |> set-leading (fsize *' 1.4) + in + let default-font ctx = + ctx |> set-font Latin (`fonts-noto-sans:NotoSans-Regular`, 1.0, 0.0) + |> set-font Kana (`fonts-noto-sans-cjk-jp:NotoSansCJKjp-Regular`, 1.0, 0.0) + |> set-font HanIdeographic (`fonts-noto-sans-cjk-jp:NotoSansCJKjp-Regular`, 1.0, 0.0) + in + let code-font ctx = + ctx |> set-font Latin (`lmmono`, 1.0, 0.0) + |> set-font Kana (`lmmono`, 1.0, 0.0) + |> set-font HanIdeographic (`lmmono`, 1.0, 0.0) + in + let bold-font ctx = + ctx |> set-font Latin (`fonts-noto-sans:NotoSans-Bold`, 1.0, 0.0) + |> set-font Kana (`fonts-noto-sans-cjk-jp:NotoSansCJKjp-Bold`, 1.0, 0.0) + |> set-font HanIdeographic (`fonts-noto-sans-cjk-jp:NotoSansCJKjp-Bold`, 1.0, 0.0) + in + + (| + font-normal = SlydifiScheme.apply-font-cfg [default-font; set-font-metrics 30pt]; + font-bold = SlydifiScheme.apply-font-cfg [bold-font]; + font-code = SlydifiScheme.apply-font-cfg [code-font]; + font-slide-title = SlydifiScheme.apply-font-cfg [bold-font; set-font-metrics 36pt]; + font-slide-author = SlydifiScheme.apply-font-cfg [default-font; set-font-metrics 24pt;]; + font-slide-date = SlydifiScheme.apply-font-cfg [default-font; set-font-metrics 20pt;]; + font-section-title = SlydifiScheme.apply-font-cfg [bold-font; set-font-metrics 28pt]; + font-frame-title = SlydifiScheme.apply-font-cfg [bold-font; set-font-metrics 28pt]; + font-pagenum = SlydifiScheme.apply-font-cfg [default-font; set-font-metrics 12pt;]; + font-footnote = SlydifiScheme.apply-font-cfg [default-font; set-font-metrics 12pt;]; + + color-bg = Color.of-css `#ffffff`; + color-fg = Color.of-css `#000000`; + color-emph = Color.of-css `#000000`; + color-link = Color.of-css `#2d539e`; + color-title = Color.of-css `#000000`; + color-separator = Color.of-css `#000000`; + color-section-ribbon = Color.of-css `#000000`; + color-section-ribbon-intersect = Color.of-css `#000000`; + + length-frame-title-height = 0pt; + length-frame-title-left-margin = 10pt; + length-frame-title-bot-margin = 10pt; + length-frame-body-top-margin = 14pt; + |) + + let config = SlydifiParam.make default-config + + let-block +set-config cfg = '< +SlydifiParam.set-param(config)(cfg); > + let-inline \set-config cfg = { \SlydifiParam.set-param(config)(cfg); } + let-block +with-config cfg bt = '< +SlydifiParam.with-param(config)(cfg)(bt); > + let-inline \with-config cfg it = { \SlydifiParam.with-param(config)(cfg)(it); } + + let-inline ctx \code code = + let config = SlydifiParam.get config in + let ctx = + ctx |> config#font-code + |> set-text-color config#color-fg + in + let fsize = get-font-size ctx in + + script-guard Latin (read-inline ctx (embed-string code)) + + let document bt = + let config = SlydifiParam.get config in + let hookf _ _ = + let numpages = SlydifiParam.get page-num in + register-cross-reference `pagecount` (arabic numpages) + in + let init-ctx ctx = + ctx |> config#font-normal + |> set-text-color config#color-fg + |> set-code-text-command (command \code) + in + SlydifiScheme.document-scheme layout init-ctx hookf bt + +% frames + + let frame-master config = + let f ctx () = + (block-nil, [ + Gr.rectangle (0pt, 0pt) (layout#paper-width, layout#paper-height) + |> fill config#color-bg + ]) + in + Frame.make layout f + + let frame-master-with-footer config = + let f ctx () = + % frame-master の graphics list を踏襲する + let (_, gr-frame-master) = frame-master config |> Frame.embed ctx () in + + let pagenum = SlydifiParam.get page-num in + let total = get-cross-reference `pagecount` |> Option.from `1` in + + let gr-page-number = + let it-pagenum = pagenum |> arabic |> embed-string in + let it-total = embed-string total in + let ctx-pagenum = config#font-pagenum ctx in + let ib = read-inline ctx-pagenum {#it-pagenum;/#it-total;} in + let pos = (xrel 1.0 -' 10pt, 12pt) in + SlydifiGraphics.put-text (1., 0.) pos ib + in + + (block-nil, [gr-frame-master; gr-page-number; ] |> List.concat) + in + Frame.make layout f + + let frame-slide-title config = + let f ctx content = + % frame-master の graphics list を踏襲する + let (_, gr-frame-master) = frame-master config |> Frame.embed ctx () in + + let title-mgn = 24pt in + + let gr-bgs = [ + Gr.rectangle (0pt, yrel 0.45) (xrel 1.0, yrel 1.0) |> fill config#color-title; + ] + in + + let gr-title = + let ctx = ctx |> config#font-slide-title |> set-text-color config#color-bg in + let iblst = content#title |> List.map (read-inline ctx) in + let pos = (xrel 0.5, yrel 0.45 +' title-mgn) in + SlydifiGraphics.put-texts + (| align = (0.5, 0.0); text-align = 0.5; leading = (get-font-size ctx *' 1.6) |) + pos iblst + in + + let gr-author = + let ctx = ctx |> config#font-slide-author |> set-text-color config#color-fg in + let iblst = content#author |> List.map (read-inline ctx) in + let pos = (xrel 0.5, yrel 0.45 -' title-mgn) in + SlydifiGraphics.put-texts + (| align = (0.5, 1.0); text-align = 0.5; leading = (get-font-size ctx *' 1.6) |) + pos iblst + in + + let gr-date = + let ctx = ctx |> config#font-slide-date |> set-text-color config#color-fg in + let iblst = content#date |> List.map (read-inline ctx) in + let pos = (xrel 0.5, yrel 0.2) in + SlydifiGraphics.put-texts + (| align = (0.5, 1.0); text-align = 0.5; leading = (get-font-size ctx *' 1.6) |) + pos iblst + in + + (block-nil, [gr-frame-master; gr-bgs; gr-title; gr-author; gr-date] |> List.concat) + in + Frame.make layout f + + let frame-section-title config = + let f ctx content = + % frame-master の graphics list を踏襲する + let (_, gr-frame-master) = frame-master config |> Frame.embed ctx () in + + % 設定 + let title-mgn = 24pt in + let ribbon-width = 5pt in + let hribbon-t = yrel 0.45 in + let hribbon-b = yrel 0.45 -' ribbon-width in + let vribbon-r = xrel 0.1 in + let vribbon-l = xrel 0.1 -' ribbon-width in + + % タイトルのグラフィックス + let gr-bgs = + let north-rect = Gr.rectangle (vribbon-l, hribbon-t) (vribbon-r, yrel 1.00) in + let south-rect = Gr.rectangle (vribbon-l, hribbon-b) (vribbon-r, yrel 0.00) in + let east-rect = Gr.rectangle (vribbon-r, hribbon-b) (xrel 1.00, hribbon-t) in + let west-rect = Gr.rectangle (vribbon-l, hribbon-b) (xrel 0.00, hribbon-t) in + let cent-rect = Gr.rectangle (vribbon-l, hribbon-b) (vribbon-r, hribbon-t) in + + [ + fill config#color-section-ribbon north-rect; + fill config#color-section-ribbon south-rect; + fill config#color-section-ribbon east-rect; + fill config#color-section-ribbon west-rect; + fill config#color-section-ribbon-intersect cent-rect; + ] + in + + let gr-titles = + let ctx = ctx |> config#font-section-title |> set-text-color config#color-fg in + let iblst = content#title |> List.map (read-inline ctx) in + let pos = (xrel 0.1 +' title-mgn, yrel 0.45 +' title-mgn) in + SlydifiGraphics.put-texts + (| align = (0.0, 0.0); text-align = 0.0; leading = (get-font-size ctx *' 1.6) |) + pos iblst + in + + (block-nil, [gr-frame-master; gr-bgs; gr-titles] |> List.concat) + in + Frame.make layout f + + let frame-normal config = + let f ctx content = + % frame-master-with-footer の graphics list を踏襲する + let (_, gr) = frame-master-with-footer config |> Frame.embed ctx () in + + % 中身のブロックボックス列 + let bb-inner = + let ctx = ctx |> config#font-normal in + let bb-blank skip = + line-break false false (ctx |> set-paragraph-margin 0pt skip) inline-fil + in + bb-blank (config#length-frame-title-height +' config#length-frame-body-top-margin) + +++ (read-block ctx content#inner) + in + + + (bb-inner, [gr] |> List.concat) + + in + Frame.make layout f + +% frame commands + + let-block ctx +make-title content = + read-block ctx '< + +SlydifiScheme.genframe(frame-slide-title (SlydifiParam.get config))(1)(content); + > + + let-block ctx +section title inner = + read-block ctx '< + +SlydifiScheme.genframe(frame-section-title (SlydifiParam.get config))(1)(|title = title;|); + #inner; + > + + let-block ctx +frame ?:n-frame title inner = + let () = page-num |> SlydifiParam.set (SlydifiParam.get page-num + 1) in + let n-frame = n-frame |> Option.from 1 in + read-block ctx '< + +SlydifiScheme.genframe(frame-normal (SlydifiParam.get config))(n-frame)(|title = title; inner = inner|); + > + +% listing + let-block +oitem dcf it bt = + '< +ghost(dcf)< +item(it)(bt); > > + +% inline commands + let-inline ctx \emph ?:dcf it = + let config = SlydifiParam.get config in + let dcf = Option.from (fun _ -> true) dcf in + let ctx2 = + SlydifiOverlay.select-from-two dcf ( + ctx |> config#font-bold + |> set-text-color config#color-emph + ) ctx + in + read-inline ctx2 it + + let-inline ctx \link ?:text url = + let config = SlydifiParam.get config in + match text with + | Some(text) -> + let ctx = + ctx |> set-text-color config#color-link + in + read-inline ctx {\href(url)(text);} + | None -> + let text = embed-string url in + let ctx = + ctx + |> config#font-code + |> set-text-color config#color-link + in + read-inline ctx {\href(url)(text);} + +% \footnote{} command + + let-inline ctx \footnotetext ref-label it = + let config = SlydifiParam.get config in + let bbf num = + let it-num = embed-string (arabic num) in + let ctx = config#font-footnote ctx in + line-break false false ctx (read-inline ctx {#it-num; #it;} ++ inline-fil) + in + FootnoteScheme.main ctx (Some(ref-label)) (fun _ -> inline-nil) bbf + + let-inline ctx \footnotemark ref-label = + let size = get-font-size ctx in + let ctx = + ctx |> set-font-size (size *' 0.75) + |> set-manual-rising (size *' 0.25) + in + let ctx = + ctx |> set-font-size (size *' 0.75) + |> set-manual-rising (size *' 0.25) + in + match FootnoteScheme.get-footnote-num ref-label with + | Some(n) -> + let it-num = embed-string n in + read-inline ctx {\*#it-num;} + | _ -> + read-inline ctx {\*?} + + let-inline ctx \footnote it = + let config = SlydifiParam.get config in + let size = get-font-size ctx in + let ibf num = + let it-num = embed-string (arabic num) in + let ctx = + ctx |> set-font-size (size *' 0.75) + |> set-manual-rising (size *' 0.25) + in + read-inline ctx {\*#it-num;} + in + let bbf num = + let it-num = embed-string (arabic num) in + let ctx = config#font-footnote ctx in + line-break false false ctx (read-inline ctx {#it-num; #it;} ++ inline-fil) + in + FootnoteScheme.main ctx None ibf bbf + +end + +let document = SlydifiMyTheme.document |
