これは に関するディスカッションです。https://discuss.python.org/t/speed-up-shutil-copytree/62078 を参照してください。何かアイデアがあれば、ぜひ送ってください!
shutil は Python の非常に便利なモジュールです。これは github で見つけることができます: https://github.com/python/cpython/blob/master/Lib/shutil.py
shutil.copytreeは、フォルダーを別のフォルダーにコピーする関数です。
この関数では、_copytree関数を呼び出してコピーしています。
_copytree は何をしますか?
_copytree の速度は、ファイル数が多い場合やファイル サイズが大きい場合にはあまり速くありません。
ここでテストします:
import os import shutil os.mkdir('test') os.mkdir('test/source') def bench_mark(func, *args): import time start = time.time() func(*args) end = time.time() print(f'{func.__name__} takes {end - start} seconds') return end - start # write in 3000 files def write_in_5000_files(): for i in range(5000): with open(f'test/source/{i}.txt', 'w') as f: f.write('Hello World' os.urandom(24).hex()) f.close() bench_mark(write_in_5000_files) def copy(): shutil.copytree('test/source', 'test/destination') bench_mark(copy)
結果は次のようになります:
write_in_5000_files には 4.084963083267212 秒かかります
コピーには 27.12768316268921 秒かかります
コピー処理を高速化するためにマルチスレッドを使用しています。そして、関数 _copytree_single_threaded の名前を変更し、新しい関数 _copytree_multithreaded を追加します。これがcopytree_multithreadedです:
def _copytree_multithreaded(src, dst, symlinks=False, ignore=None, copy_function=shutil.copy2, ignore_dangling_symlinks=False, dirs_exist_ok=False, max_workers=4): """Recursively copy a directory tree using multiple threads.""" sys.audit("shutil.copytree", src, dst) # get the entries to copy entries = list(os.scandir(src)) # make the pool with ThreadPoolExecutor(max_workers=max_workers) as executor: # submit the tasks futures = [ executor.submit(_copytree_single_threaded, entries=[entry], src=src, dst=dst, symlinks=symlinks, ignore=ignore, copy_function=copy_function, ignore_dangling_symlinks=ignore_dangling_symlinks, dirs_exist_ok=dirs_exist_ok) for entry in entries ] # wait for the tasks for future in as_completed(futures): try: future.result() except Exception as e: print(f"Failed to copy: {e}") raise
マルチスレッドを使用するかどうかの判断を追加します。
if len(entries) >= 100 or sum(os.path.getsize(entry.path) for entry in entries) >= 100*1024*1024: # multithreaded version return _copytree_multithreaded(src, dst, symlinks=symlinks, ignore=ignore, copy_function=copy_function, ignore_dangling_symlinks=ignore_dangling_symlinks, dirs_exist_ok=dirs_exist_ok) else: # single threaded version return _copytree_single_threaded(entries=entries, src=src, dst=dst, symlinks=symlinks, ignore=ignore, copy_function=copy_function, ignore_dangling_symlinks=ignore_dangling_symlinks, dirs_exist_ok=dirs_exist_ok)
ソース フォルダーに 50000 個のファイルを書き込みます。ベンチマーク:
def bench_mark(func, *args): import time start = time.perf_counter() func(*args) end = time.perf_counter() print(f"{func.__name__} costs {end - start}s")
書き込み:
import os os.mkdir("Test") os.mkdir("Test/source") # write in 50000 files def write_in_file(): for i in range(50000): with open(f"Test/source/{i}.txt", 'w') as f: f.write(f"{i}") f.close()
2 つの比較:
def copy1(): import shutil shutil.copytree('test/source', 'test/destination1') def copy2(): import my_shutil my_shutil.copytree('test/source', 'test/destination2')
コピー 1 のコストは 173.04780609999943 秒
コピー 2 のコストは 155.81321870000102s
copy2 は copy1 よりもはるかに高速です。何度でも実行できます。
マルチスレッドを使用すると、コピー処理を高速化できます。ただし、メモリ使用量が増加します。ただし、コード内のマルチスレッドを書き直す必要はありません。
「バリー・スコット」に感謝します。私は彼/彼女の提案に従います:
非同期 I/O を使用すると、オーバーヘッドを減らして同じ改善が得られる可能性があります。
次のコードを書きます:
import os import shutil import asyncio from concurrent.futures import ThreadPoolExecutor import time # create directory def create_target_directory(dst): os.makedirs(dst, exist_ok=True) # copy 1 file async def copy_file_async(src, dst): loop = asyncio.get_event_loop() await loop.run_in_executor(None, shutil.copy2, src, dst) # copy directory async def copy_directory_async(src, dst, symlinks=False, ignore=None, dirs_exist_ok=False): entries = os.scandir(src) create_target_directory(dst) tasks = [] for entry in entries: src_path = entry.path dst_path = os.path.join(dst, entry.name) if entry.is_dir(follow_symlinks=not symlinks): tasks.append(copy_directory_async(src_path, dst_path, symlinks, ignore, dirs_exist_ok)) else: tasks.append(copy_file_async(src_path, dst_path)) await asyncio.gather(*tasks) # choose copy method def choose_copy_method(entries, src, dst, **kwargs): if len(entries) >= 100 or sum(os.path.getsize(entry.path) for entry in entries) >= 100 * 1024 * 1024: # async version asyncio.run(copy_directory_async(src, dst, **kwargs)) else: # single thread version shutil.copytree(src, dst, **kwargs) # test function def bench_mark(func, *args): start = time.perf_counter() func(*args) end = time.perf_counter() print(f"{func.__name__} costs {end - start:.2f}s") # write in 50000 files def write_in_50000_files(): for i in range(50000): with open(f"Test/source/{i}.txt", 'w') as f: f.write(f"{i}") def main(): os.makedirs('Test/source', exist_ok=True) write_in_50000_files() # 单线程复制 def copy1(): shutil.copytree('Test/source', 'Test/destination1') def copy2(): shutil.copytree('Test/source', 'Test/destination2') # async def copy3(): entries = list(os.scandir('Test/source')) choose_copy_method(entries, 'Test/source', 'Test/destination3') bench_mark(copy1) bench_mark(copy2) bench_mark(copy3) shutil.rmtree('Test') if __name__ == "__main__": main()
出力:
コピー 1 のコストは 187.21 秒
コピー 2 のコストは 244.33 秒
コピー 3 のコストは 111.27 秒
非同期バージョンの方がシングルスレッドバージョンよりも高速であることがわかります。ただし、シングル スレッド バージョンはマルチスレッド バージョンよりも高速です。 (私のテスト環境があまり良くないかもしれません。試して結果を私に返信して送ってください)
バリー・スコット、ありがとう!
非同期は良い選択です。しかし、完璧な解決策はありません。何か問題が見つかった場合は、私に返信してください。
python.org でディスカッションを書くのはこれが初めてです。何か問題がございましたら、お知らせください。ありがとう。
私のGithub: https://github.com/mengqinyuan
私の開発者: https://dev.to/mengqinyuan
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3