Dies ist eine Diskussion über , siehe: https://discuss.python.org/t/speed-up-shutil-copytree/62078. Wenn Sie Ideen haben, senden Sie mir diese bitte!
shutil ist ein sehr nützliches Modul in Python. Sie finden es in Github: https://github.com/python/cpython/blob/master/Lib/shutil.py
shutil.copytree ist eine Funktion, die einen Ordner in einen anderen Ordner kopiert.
In dieser Funktion wird die Funktion _copytree zum Kopieren aufgerufen.
Was macht _copytree?
_Die Copytree-Geschwindigkeit ist nicht sehr hoch, wenn die Anzahl der Dateien groß ist oder die Dateigröße groß ist.
Hier testen:
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)
Das Ergebnis ist:
write_in_5000_files dauert 4,084963083267212 Sekunden
Das Kopieren dauert 27,12768316268921 Sekunden
Ich verwende Multithread, um den Kopiervorgang zu beschleunigen. Und ich benenne die Funktion in _copytree_single_threaded um und füge eine neue Funktion hinzu: _copytree_multithreaded. Hier ist der 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
Ich füge ein Urteil hinzu, um zu entscheiden, ob Multithread verwendet werden soll oder nicht.
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)
Ich schreibe 50000 Dateien in den Quellordner. Benchmark:
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")
Schreiben:
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()
Zwei im Vergleich:
def copy1(): import shutil shutil.copytree('test/source', 'test/destination1') def copy2(): import my_shutil my_shutil.copytree('test/source', 'test/destination2')
Kopie1 kostet 173,04780609999943s
copy2 kostet 155,81321870000102s
copy2 ist um einiges schneller als copy1. Sie können mehrmals ausführen.
Die Verwendung von Multithread kann den Kopiervorgang beschleunigen. Es wird jedoch die Speichernutzung erhöhen. Aber wir müssen den Multithread im Code nicht neu schreiben.
Danke an „Barry Scott“. Ich werde seinem/ihrem Vorschlag folgen:
Möglicherweise erzielen Sie die gleiche Verbesserung bei geringerem Overhead, wenn Sie asynchrone E/A verwenden.
Ich schreibe diesen Code:
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()
Ausgabe:
Kopie1 kostet 187,21 Sek.
copy2 kostet 244,33s
copy3 kostet 111,27s
Sie können sehen, dass die asynchrone Version schneller ist als die Single-Thread-Version. Aber die Single-Thread-Version ist schneller als die Multi-Thread-Version. (Vielleicht ist meine Testumgebung nicht sehr gut, Sie können versuchen, mir Ihr Ergebnis als Antwort zu senden)
Vielen Dank, Barry Scott!
Async ist eine gute Wahl. Aber keine Lösung ist perfekt. Wenn Sie ein Problem finden, können Sie mir eine Antwort senden.
Dies ist das erste Mal, dass ich eine Diskussion auf python.org schreibe. Wenn es ein Problem gibt, lassen Sie es mich bitte wissen. Danke schön.
Mein Github: https://github.com/mengqinyuan
Mein Dev.to: https://dev.to/mengqinyuan
Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.
Copyright© 2022 湘ICP备2022001581号-3