Il s'agit d'une discussion sur , voir : https://discuss.python.org/t/speed-up-shutil-copytree/62078. Si vous avez des idées, envoyez-moi s'il vous plaît !
shutil est un module très utile en Python. Vous pouvez le trouver sur github : https://github.com/python/cpython/blob/master/Lib/shutil.py
shutil.copytree est une fonction qui copie un dossier dans un autre dossier.
Dans cette fonction, il appelle la fonction _copytree pour copier.
Que fait _copytree ?
_copytree n'est pas très rapide lorsque le nombre de fichiers est important ou que la taille du fichier est importante.
Testez ici :
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)
Le résultat est :
write_in_5000_files prend 4,084963083267212 secondes
la copie prend 27,12768316268921 secondes
J'utilise le multithread pour accélérer le processus de copie. Et je renomme la fonction _copytree_single_threaded et ajoute une nouvelle fonction _copytree_multithreaded. Voici le 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
J'ajoute un jugement pour choisir d'utiliser ou non le multithread.
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)
J'écris 50 000 fichiers dans le dossier source. Repère de nivellement:
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")
Écrivez :
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()
Deux comparaison :
def copy1(): import shutil shutil.copytree('test/source', 'test/destination1') def copy2(): import my_shutil my_shutil.copytree('test/source', 'test/destination2')
copie1 coûte 173,04780609999943s
copy2 coûte 155,81321870000102s
copy2 est beaucoup plus rapide que copy1. Vous pouvez courir plusieurs fois.
L'utilisation du multithread peut accélérer le processus de copie. Mais cela augmentera l'utilisation de la mémoire. Mais nous n'avons pas besoin de réécrire le multithread dans le code.
Merci à "Barry Scott". Je vais suivre sa suggestion :
Vous pourriez obtenir la même amélioration pour moins de frais généraux en utilisant les E/S asynchrones.
J'écris ce 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()
Sortir:
la copie 1 coûte 187,21 s
copy2 coûte 244,33 s
copy3 coûte 111,27 s
Vous pouvez voir que la version asynchrone est plus rapide que la version à thread unique. Mais la version monothread est plus rapide que la version multithread. (Peut-être que mon environnement de test n'est pas très bon, vous pouvez essayer de m'envoyer votre résultat en réponse)
Merci Barry Scott !
Async est un bon choix. Mais aucune solution n’est parfaite. Si vous rencontrez un problème, vous pouvez m'envoyer une réponse.
C'est la première fois que j'écris une discussion sur python.org. S'il y a un problème, faites-le-moi savoir. Merci.
Mon Github : https://github.com/mengqinyuan
Mon Dev.to : https://dev.to/mengqinyuan
Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.
Copyright© 2022 湘ICP备2022001581号-3