静かなる名辞

pythonとプログラミングのこと


Pythonで遅いサブプロセスをスレッド並列でたくさん叩く

概要

 いつ使うんだと言われてしまうPythonのスレッドですが、Pythonの外で遅い原因があるときは高速化に威力を発揮します。

 たとえばこんな感じです。言語はbashです。

#!/bin/bash

sleep 3
echo "hoge"

 特にひねりはありません。slow_command.shとでもして保存しておきます。

 Pythonから呼ぶと、

import subprocess

for _ in range(5):
    result = subprocess.run(["./slow_command.sh"], stdout=subprocess.PIPE)
    print(result.stdout.decode())

 いい加減ですがこんな感じでしょうか。当たり前のように15秒かけて実行してくれます。

ThreadPoolExecutorを使う

  • CPUバウンドではない
  • 遅いのはPythonではない

 といった条件が揃っているので、Pythonのスレッドを使う良い機会です。

 threading直叩きはこの場合つらいものがあるので、concurrent.futures.ThreadPoolExecutorを使います。

concurrent.futures -- 並列タスク実行 — Python 3.8.2 ドキュメント

import subprocess
from concurrent.futures import ThreadPoolExecutor

def f():
    result = subprocess.run(["./slow_command.sh"], stdout=subprocess.PIPE)
    return result.stdout.decode()

with ThreadPoolExecutor(max_workers=5) as pool:
    futures = [pool.submit(f) for _ in range(5)]
    for f in futures:
        print(f.result())

 今度はちゃんと5秒で終わります。

asyncio

 asyncioはこれまでとっつきづらい気がしてほとんど触ってこなかったのですが、やればできるんじゃないでしょうか。

python - How to use asyncio with existing blocking library? - Stack Overflow

 まだ試していないので、そのうち試してみて記事をアップデートします(か、別記事になるかもしれない)。

使いみち

 遅いサブプロセスの結果を大量に受けるときはありです。

 スレッド並列といってもけっきょく子プロセスが生えてしまうので、ワーカーの数には気をつけた方が良いでしょう(同時に子プロセス生やしすぎてメモリエラー、とか)。