web-dev-qa-db-ja.com

複数のディレクトリを同時に再帰的にスキャンする速度を向上させるにはどうすればよいですか?

だから私は並行性および/またはマルチスレッド化および/またはプロセスの並列性を使用してプログラムを高速化しようとしています。トピックはかなり複雑で、私は彼らにとっては初めてなので、どちらをいつ使用するかを理解しようとしています。

私のタスク(むしろサブタスク):

  1. UNIXディレクトリのサイズを再帰的に取得します。実際、私は一度に複数のディレクトリを処理します。

私の理解に基づくと、ディレクトリのスキャンはI/Oにバインドされたプロセスであり、その結果、複数のプロセスではなくスレッドを使用することにしました。

これが私が試したものです(関数は機能しますが、結果は実際に私が期待するものではありません):

私のディレクトリスキャン機能-utils.py:

_def get_path_size(path):
    """Returns total size of a file/directory.

    Args:
        path: File/directory path.

    Returns:
        Total size of a path in bits.

    """
    # Size in bytes/bits (B).
    total = 0

    if os.path.isdir(path):
        with os.scandir(path) as direc:
            for entry in direc:
                if entry.is_dir(follow_symlinks=False):
                    total += get_path_size(entry.path)
                else:
                    total += entry.stat(follow_symlinks=False).st_size
    else:
        total += os.stat(path).st_size

    return total 
_

これが上記の関数を呼び出す私のマルチスレッド関数です-file1.py:

_import concurrent.futures

def conc(self):
    reqs = [{'path': '/path/to/disk1'}, {'path': '/path/to/disk2'}]

    with concurrent.futures.ThreadPoolExecutor(max_workers=12) as executor:
        future_to_path = {
            executor.submit(utils.get_path_size, req['path']): req for req in reqs
        }

        for future in concurrent.futures.as_completed(future_to_path):
            path = future_to_path[future]
            size = future.result()
            print(path, size)
_

そして、これがプロセスの並列処理を使用する私の関数です-file2.py:

_import concurrent.futures

def paral():
    with concurrent.futures.ProcessPoolExecutor(max_workers=6) as executor:
            for path, size in Zip(PATHS, executor.map(get_path_size, PATHS)):
                    print(path, size)
_

私が疑問を抱く理由は、プログラムがProcessPoolExecutorではなくThreadPoolExecutorを使用してより速く(速くはない場合、ほぼ同じ)終了するように見えるためです。 get_path_size()はかなりI/O集約的であり、 docsThreadPoolExecutorがI/O作業により適していると言っている私の理解に基づいて、 paral()の実行が速くなるのは驚くべきことです。

私の質問:

  1. 私はそれを全体的に正しくしていますか?つまり、ProcessPoolExecutorまたはThreadPoolExecutorを使用する必要がありますか?
  2. このコードをより良い/より速くする方法などに関する他の提案はありますか?

編集#1-テスト結果:

私は3つのオプションのそれぞれに対して5つのテストを実行しました(各テストは、ロードされていないマシンで次々に実行されました):non-parallel、ProcessPoolExecutor、およびThreadPoolExecutor

このテストでは、すべてのディレクトリの合計サイズは65GBでした。昨日、私はこれらのテストを合計サイズが1.5TBのディレクトリで実行しましたが、結果は比較的同じでした。

機械スペック:

_CPU(s):                20
Thread(s) per core:    1
Core(s) per socket:    10
Socket(s):             2
_

非並列実行時間:

_Duration 38.25443077087402 seconds
Duration 16.98011016845703 seconds
Duration 21.282278299331665 seconds
Duration 37.90052556991577 seconds
Duration 40.511338233947754 seconds
_

ProcessPoolExecutor

_Duration 7.311123371124268 seconds
Duration 15.097688913345337 seconds
Duration 15.133012056350708 seconds
Duration 13.949966669082642 seconds
Duration 4.563556671142578 seconds
_

ThreadPoolExecutor

_Duration 28.408297300338745 seconds
Duration 7.303474187850952 seconds
Duration 26.91611957550049 seconds
Duration 4.6026129722595215 seconds
Duration 3.424044370651245 seconds
_
3
tera_789

最初に理解することは、スレッド処理isが並列処理の形式であることです。個別のスレッドと個別のプロセスの違いは、この場合それほど重要ではありません

自分で書くと、これはI/Oに大きく依存するプロセスです。実際、I/Oアクセスの間に実行されるコードは測定可能な影響を与えないほど重くなっています。そのため、並列処理に対するさまざまなアプローチの間に大きな違いが見つかることは期待できません。しかし、あなたが尋ねているので、ThreadPoolExecutorはProcessPoolExecutorの5倍のワーカーを割り当てます。これらのワーカーはすべて、実際の利益がなくてもオーバーヘッドが発生するだけなので(I/Oが1つまたは2つのディスクを通過することによって制限されます)、ProcessPoolExecutorがわずかに有利になります。 (労働者の数を減らしてみてください。違いはなくなると思います)。

ユースケースで並列処理を利用する唯一の方法は、I/Oワークロードを複数のハードディスク/ストレージデバイスに分割できる場合で、デバイスごとに1つのスレッド/プロセスを使用します。

4
Tfry

いくつかのアクティビティが正式にI/Oバウンドであっても、並列化できないことを意味しません。根本的には限界ですが表現力豊かな例として、テープドライバーから何かを読み取る必要があるとします。テープシークは平均5分です。それぞれが独自のドライバー(デバイス)にインストールされている2つの異なるテープから何かを読み取る必要があります。リクエストを並行して発行すると、平均時間は約5分になります。順次リクエストを発行する場合、結果時間は10分です。

私が正しく理解していれば、あなたのケースは同じリクエストセットですが、異なるプロセスではなく単一のプロセスです。一見すると、カーネルI/Oスケジューラーはスレッドとプロセスを区別し、プロセスごとのバケットである種のI/O帯域幅制限を提供していると思います。別のバリアントは、実装がPythonとCランドの間の適切な移行に費やしすぎていることです。しかし、これらはすべて、実際の事実のない単なる推測にすぎません。

問題は、パフォーマンスが本当に難しいということです。人々は何年もかけてコードを調整し、すべてに影響する小さな詳細を見つけるために、またはレイヤー全体を書き換えて1〜2%の高速化を達成するために費やしています。そしてその後、下位層(CPU、カーネルなど)の次の変更により、これらの結果がすべて無効になる可能性があります。したがって、差が30%未満の場合は、今のところ最もよく見えるバリアントを選択し、別のタスクに切り替えます:)

2
Netch

単なる警告:実行時間を測定していますが、明らかに他のコードが同時に実行されていません。しかし、あなたは独力ではありません。また、同じハードウェアで実行されている他のコードにどのように影響するかを考慮する必要があります。タスクを並行して実行すると、実行時間の半分になり、ネットワークトラフィック全体が4倍になった場合、他のすべての人が苦しむため、これは良い解決策ではありません。

そしてもちろん、あなたがそれをやった場合にどれだけ時間がかかるかは誰も気にしませんonce。したがって、連続した実行で行われる合計作業量が少なくなるようにデータをキャッシュする方法があるかどうかを考えます。

0
gnasher729