91 views
【1】やりたいこと
重たい処理を複数プロセスに分担させ、高速実行したい場合はよくある。
このとき、
マルチプロセスで実行している処理の進捗状況を、1本のプログレスバーに表示したい
と思った。
過去記事 (131) tqdm.trangeで簡単にプログレスバーを表示する。 でインストールしたばかりの
tqdmを使えばこれが簡単に実現できる らしいと聞いたので、試してみることにした。
【2】やってみた
1) プログラムソースコード
以下のコマンドラインオプションを用意した。
option | type | 説明 |
---|---|---|
-n | int | 並列実行プロセス数を指定する。 |
from multiprocessing import Pool from itertools import product from tqdm import tqdm # 進捗バー表示用 from datetime import datetime # 現在日時を取得するためのライブラリ import argparse #/////////////////////////////////////////////////////////////////////////////// # 各プロセスに実行させるダミーの重たい処理 def heavy_calculation(n): # nを素因数分解する(単純な試し割り法) factors = [] i = 2 while i * i <= n: while n % i == 0: factors.append(i) n //= i i += 1 if n > 1: factors.append(n) return factors #/////////////////////////////////////////////////////////////////////////////// # 各プロセスが分担する処理のエントリポイント def processProc( param ): vA, vB = param factors = [] for i in range(1000): n = (vA + vB) * (i + 1) * 10**6 factors += heavy_calculation(n) return sum(factors) # 素因数の合計を返す #/////////////////////////////////////////////////////////////////////////////// if __name__ == "__main__": #--------------------------------------------------------------------------- # コマンドライン引数を取得 parser = argparse.ArgumentParser(description="test") parser.add_argument('-n', type=int, help='プロセス数', default=1) args = parser.parse_args() #--------------------------------------------------------------------------- # データ初期化 n_process = args.n # 並列で動かすプロセス数 vAs = range(1, 101) # 要素数100個の配列(1,2,3,4,...,98,99,100) vBs = range(1, 101) # 要素数100個の配列(1,2,3,4,...,98,99,100) # vAsと vBsの全要素の組合せ(100×100=10000通り)を作成 params = list(product(vAs, vBs)) n_params = len(params) total_sum = 0 # 全結果の合計値を格納 start_time = datetime.now() # 開始時刻を記録 #--------------------------------------------------------------------------->>> 並列処理区間 with Pool( n_process ) as pool: # プロセスプールを作成 # paramsの 1要素ずつ各プロセスに処理させる。 results_iterator = pool.imap_unordered( processProc, params ) # tqdmで進捗バーを表示する。 for res in tqdm( results_iterator, total=n_params ): total_sum += res # 各結果を合計に加算 #---------------------------------------------------------------------------<<< 並列処理区間 end_time = datetime.now() # 終了時刻を記録 duration = end_time - start_time # 最終結果を表示 print(f"全組合せの加算結果の合計値: {total_sum}") # 正しく処理されたことを確認するため print(f"実行時間: {duration.total_seconds():.1f}") # 並列化で高速化することを確認
2) 実行結果
実行環境の CPUが Intel Core Ultla 265KF(20core) なので、最大 20コア指定で実行してみた。
20コア、15コア、10コア、8コア、4コア、2コア、1コア 指定で上記のプログラムを実行してみた。
いずれの場合も同じように 1本のプログレスバーが伸びる様子を確認 できた。
$ python main_00133.py -n 20 100%|█████████████████████████████████████████████| 10000/10000 [00:00<00:00, 14855.35it/s] 全組合せの加算結果の合計値: 2248942000 実行時間: 0.7 sec $ python main_00133.py -n 15 100%|█████████████████████████████████████████████| 10000/10000 [00:00<00:00, 12148.88it/s] 全組合せの加算結果の合計値: 2248942000 実行時間: 0.8 sec $ python main_00133.py -n 10 100%|█████████████████████████████████████████████| 10000/10000 [00:01<00:00, 8506.88it/s] 全組合せの加算結果の合計値: 2248942000 実行時間: 1.2 sec $ python main_00133.py -n 8 100%|█████████████████████████████████████████████| 10000/10000 [00:01<00:00, 7049.92it/s] 全組合せの加算結果の合計値: 2248942000 実行時間: 1.4 sec $ python main_00133.py -n 4 100%|█████████████████████████████████████████████| 10000/10000 [00:02<00:00, 3530.27it/s] 全組合せの加算結果の合計値: 2248942000 実行時間: 2.8 sec $ python main_00133.py -n 2 100%|█████████████████████████████████████████████| 10000/10000 [00:05<00:00, 1749.37it/s] 全組合せの加算結果の合計値: 2248942000 実行時間: 5.7 sec $ python main_00133.py -n 1 100%|█████████████████████████████████████████████| 10000/10000 [00:11<00:00, 893.68it/s] 全組合せの加算結果の合計値: 2248942000 実行時間: 11.2 sec
プログレスバーの右端に表示されている 893.68it/s は、
1秒当たりの実行された iteration数 だ。
今回の場合、全10000セットのデータを 1秒につき何セット処理できたかを表す。
例えば、2コア指定で実行した場合の 1749.37it/s を確かめてみると、
10000回 ÷ 5.7秒 = 1754.3回/秒
だいたい同じだ。
プログレスバーは、進捗状況をざっくりと眺めることが目的の indicator なので、時間値の精度は気にしない。
アクセス数(直近7日): ※試験運用中、BOT除外簡易実装済2025-07-11: 0回 2025-07-10: 2回 2025-07-09: 2回 2025-07-08: 3回 2025-07-07: 0回 2025-07-06: 0回 2025-07-05: 0回