私は大量のデータ処理時にPythonのmultiprocessingでお手化並列化をしておりますが, メモリをドカ食いして計算が止まるという事象に頻繁に遭遇して悲しみに包まれておりました。
実はこれ散々既出のようで, プロセスをforkではなくspawnで生成すると良いとの記事がありました。
しかし背景知識がないためfork…?spawn…?コレガワカラナイ。
ということでこの記事はこれらの違いをざっくり理解することを目的とします。
忙しい人のためのざっくりした結論
Fork
- 親プロセスをそっくりそのままコピーするので立ち上げが早い
- 全部コピーするのでメモリを圧迫する
Spawn
- 親プロセスをコピーするのではなくスクラッチで新たにインタープリタを起動し直すので立ち上げが遅い
- 親プロセスから子プロセスに必要な情報だけ受け継ぐのでメモリを圧迫しない
ForkとSpawnの違いとは
ほぼFork vs Spawn in Python Multiprocessingの和訳です。
Forkとspawnの共通点
- 子プロセスと親プロセスは独立しており, お互いにスレッドや変数の受け渡しをすることはできない
Fork
- Forkで生成された子プロセスは親プロセスの全ての変数とその状態を受け継ぐ
(なんと受け継いだ変数はオブジェクトIDまで一緒らしい。しかしこれらが同じ対象を示すわけではないらしい。これは個々のプロセスは異なるアドレス空間を持つからと思われる(参考)) - Subprocess.Poolはそれぞれの子プロセスに引数を割り振る
spawn
- Spawnで生成されたプロセスは新たなpythonインタープリタを起動する
- 現在のモジュールは再読み込みされて変数も新たに作り直される
- その上でそれぞれのプロセスに引数を割り当てて並列化する
- forkと同様にそれぞれの子プロセスと親プロセスは独立している
- グローバル変数を読み込みにいかない。
なんでSpawnだとメモリ消費が抑えられるのか
ほぼmultiprocessing fork() vs spawn()の和訳です。
Fork
- 親プロセスのインタプリタ, オブジェクト, モジュールなどを全てコピーする
(スレッドはコピーしない) - 子プロセスが親プロセスのほぼ完全コピーなのでメモリを圧迫する
- スレッド関連でデッドロックが起きて危険
Spawn
- 新たに子プロセス用のインタープリタを立ち上げる
- 立ち上げたpython上で随時モジュールや関数を実行していく
- その際に必要なモジュールや関数を親プロセスから随時受け継ぐ(ここ自信ない)
- 従ってメモリが圧迫されない
結局どっちを使うか
- 小規模・短期間の処理ならどちらでも良さそう
- 長時間の並列処理ならSpawnを使っておいたほうが無難。memory not allocatedなどの予期せぬエラーで時間を無駄にせずに済む