並列時の乱数生成器の挙動実験 - Python編 -
multiprocessing時のrandomの挙動について
pythonのmultiprocessingモジュールで並列処理を行った時に、random.Randomでインスタンス化した疑似乱数生成器がどのような挙動をするのか実験しました。
乱数生成器のインスタンスが生成する乱数が
- プロセス毎で異なっているのか
- 全プロセスで同じ値なのか
この観点で実験します。
実験1 seedを指定した場合
乱数生成器インスタンス化する時にseedを指定した場合の挙動を調べます。
multi-random-with-seed.pyCopied!!from multiprocessing import Process import random def generate_random_value(process_id: int, generator: random.Random): for i in range(5): # 0 ~ 100の範囲でランダムな整数を生成する value = generator.randint(0, 100) print(f"index={i}, process_id={process_id}, value={value}") def main(): # seedを指定する rnd_generator = random.Random(10) n_process = 10 processes = [] for process_id in range(n_process): # 各プロセスの引数に予め作成したrnd_generatorを渡しています p = Process( target=generate_random_value, args=( process_id, rnd_generator, ), ) processes.append(p) for p in processes: p.start() for p in processes: p.join() if __name__ == "__main__": main()
実行結果
実行結果より次のことがわかります。
- 各プロセスの各indexで生成したvalueが同じであること
これより、各プロセスにおける乱数生成の挙動は同じであることがわかります。つまり、各プロセスは独立して乱数生成器の内部状態を保持していることになります。
実験1の実行結果Copied!!$ python3 multi-random-with-seed.py | sort index=0, process_id=0, value=73 index=0, process_id=1, value=73 index=0, process_id=2, value=73 index=0, process_id=3, value=73 index=0, process_id=4, value=73 index=0, process_id=5, value=73 index=0, process_id=6, value=73 index=0, process_id=7, value=73 index=0, process_id=8, value=73 index=0, process_id=9, value=73 index=1, process_id=0, value=4 index=1, process_id=1, value=4 index=1, process_id=2, value=4 index=1, process_id=3, value=4 index=1, process_id=4, value=4 index=1, process_id=5, value=4 index=1, process_id=6, value=4 index=1, process_id=7, value=4 index=1, process_id=8, value=4 index=1, process_id=9, value=4 index=2, process_id=0, value=54 index=2, process_id=1, value=54 index=2, process_id=2, value=54 index=2, process_id=3, value=54 index=2, process_id=4, value=54 index=2, process_id=5, value=54 index=2, process_id=6, value=54 index=2, process_id=7, value=54 index=2, process_id=8, value=54 index=2, process_id=9, value=54 index=3, process_id=0, value=61 index=3, process_id=1, value=61 index=3, process_id=2, value=61 index=3, process_id=3, value=61 index=3, process_id=4, value=61 index=3, process_id=5, value=61 index=3, process_id=6, value=61 index=3, process_id=7, value=61 index=3, process_id=8, value=61 index=3, process_id=9, value=61 index=4, process_id=0, value=73 index=4, process_id=1, value=73 index=4, process_id=2, value=73 index=4, process_id=3, value=73 index=4, process_id=4, value=73 index=4, process_id=5, value=73 index=4, process_id=6, value=73 index=4, process_id=7, value=73 index=4, process_id=8, value=73 index=4, process_id=9, value=73
実験2 seedを指定しない場合
乱数生成器インスタンス化する時にseedを指定しない場合の挙動を調べます。
multi-random-non-seed.pyCopied!!from multiprocessing import Process import random def generate_random_value(process_id: int, generator: random.Random): for i in range(5): # 0 ~ 100の範囲でランダムな整数を生成する value = generator.randint(0, 100) print(f"index={i}, process_id={process_id}, value={value}") def main(): # seedを指定しない rnd_generator = random.Random() n_process = 10 processes = [] for process_id in range(n_process): # 各プロセスの引数に予め作成したrnd_generatorを渡しています p = Process( target=generate_random_value, args=( process_id, rnd_generator, ), ) processes.append(p) for p in processes: p.start() for p in processes: p.join() if __name__ == "__main__": main()
実行結果
実行結果より次のことがわかります。
- 各プロセスの各indexで生成したvalueが同じであること
- 実行時によってvalueは異なりますが、indexが等しいもの同士はvalueの値が等しいことが確認できます。
この結果からも、各プロセスにおける乱数生成の挙動は同じであることがわかります。つまり、各プロセスは独立して乱数生成器の内部状態を保持していることになります。
実験2の実行結果Copied!!$ python3 multi-random-non-seed.py | sort index=0, process_id=0, value=68 index=0, process_id=1, value=68 index=0, process_id=2, value=68 index=0, process_id=3, value=68 index=0, process_id=4, value=68 index=0, process_id=5, value=68 index=0, process_id=6, value=68 index=0, process_id=7, value=68 index=0, process_id=8, value=68 index=0, process_id=9, value=68 index=1, process_id=0, value=92 index=1, process_id=1, value=92 index=1, process_id=2, value=92 index=1, process_id=3, value=92 index=1, process_id=4, value=92 index=1, process_id=5, value=92 index=1, process_id=6, value=92 index=1, process_id=7, value=92 index=1, process_id=8, value=92 index=1, process_id=9, value=92 index=2, process_id=0, value=12 index=2, process_id=1, value=12 index=2, process_id=2, value=12 index=2, process_id=3, value=12 index=2, process_id=4, value=12 index=2, process_id=5, value=12 index=2, process_id=6, value=12 index=2, process_id=7, value=12 index=2, process_id=8, value=12 index=2, process_id=9, value=12 index=3, process_id=0, value=1 index=3, process_id=1, value=1 index=3, process_id=2, value=1 index=3, process_id=3, value=1 index=3, process_id=4, value=1 index=3, process_id=5, value=1 index=3, process_id=6, value=1 index=3, process_id=7, value=1 index=3, process_id=8, value=1 index=3, process_id=9, value=1 index=4, process_id=0, value=86 index=4, process_id=1, value=86 index=4, process_id=2, value=86 index=4, process_id=3, value=86 index=4, process_id=4, value=86 index=4, process_id=5, value=86 index=4, process_id=6, value=86 index=4, process_id=7, value=86 index=4, process_id=8, value=86 index=4, process_id=9, value=86
まとめ
本実験では、並列処理時における各プロセスの乱数生成の挙動を調べました。結論は次の通りです。
- 各プロセスで独立した乱数生成の挙動をする
- 言い換えると「プロセス間で乱数生成器の内部状態を共有していない」になります。
モジュール
Appendix - インスタンス化もseedもしない場合
実験1では乱数生成器をインスタンス化していましたが、今回はインスタンス化、seedも指定しない場合の挙動を見てみましょう。
- 本実験は「seed値が何か」という問題に帰結しているので本筋とは少し違います。なのでAppendixとして記載しています。
multi-random-non-seed.pyCopied!!from multiprocessing import Process import random def generate_random_value(process_id: int): for i in range(5): # 0 ~ 100の範囲でランダムな整数を生成する value = random.randint(0, 100) print(f"index={i}, process_id={process_id}, value={value}") def main(): n_process = 10 processes = [] for process_id in range(n_process): p = Process( target=generate_random_value, args=(process_id,), ) processes.append(p) for p in processes: p.start() for p in processes: p.join() if __name__ == "__main__": main()
結果
実行結果より次のことがわかります。
- 各プロセスの各indexで生成した乱数が異なっていること
これは乱数生成時にseedの値として現在時刻を使っているためです。random.seed
Copied!!$ python3 multi-random-non-seed.py index=0, process_id=0, value=15 index=1, process_id=0, value=75 index=2, process_id=0, value=64 index=3, process_id=0, value=89 index=4, process_id=0, value=36 index=0, process_id=1, value=73 index=1, process_id=1, value=86 index=2, process_id=1, value=21 index=3, process_id=1, value=37 index=4, process_id=1, value=55 index=0, process_id=2, value=78 index=1, process_id=2, value=38 index=2, process_id=2, value=57 index=3, process_id=2, value=95 index=4, process_id=2, value=39 index=0, process_id=3, value=98 index=1, process_id=3, value=21 index=2, process_id=3, value=100 index=3, process_id=3, value=27 index=4, process_id=3, value=71 index=0, process_id=4, value=78 index=1, process_id=4, value=97 index=2, process_id=4, value=48 index=3, process_id=4, value=29 index=4, process_id=4, value=58 index=0, process_id=5, value=97 index=1, process_id=5, value=90 index=2, process_id=5, value=6 index=3, process_id=5, value=19 index=4, process_id=5, value=47 index=0, process_id=6, value=47 index=1, process_id=6, value=3 index=2, process_id=6, value=6 index=3, process_id=6, value=64 index=4, process_id=6, value=91 index=0, process_id=7, value=48 index=1, process_id=7, value=35 index=2, process_id=7, value=45 index=3, process_id=7, value=13 index=4, process_id=7, value=53 index=0, process_id=8, value=95 index=1, process_id=8, value=25 index=2, process_id=8, value=84 index=3, process_id=8, value=43 index=4, process_id=8, value=4 index=0, process_id=9, value=32 index=1, process_id=9, value=46 index=2, process_id=9, value=25 index=3, process_id=9, value=94 index=4, process_id=9, value=9