이것은 에 대한 토론입니다. 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()
두 가지 비교:
def copy1(): import shutil shutil.copytree('test/source', 'test/destination1') def copy2(): import my_shutil my_shutil.copytree('test/source', 'test/destination2')
copy1 비용은 173.04780609999943s
copy2 비용은 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()
출력:
copy1의 비용은 187.21초
copy2 비용은 244.33초
copy3 비용은 111.27초입니다
싱글 스레드 버전보다 비동기 버전이 더 빠른 것을 확인할 수 있습니다. 그러나 단일 스레드 버전은 다중 스레드 버전보다 빠릅니다. (제 테스트 환경이 좋지 않을 수도 있으니, 결과를 답장으로 보내주시면 됩니다.)
Barry Scott에게 감사드립니다!
비동기는 좋은 선택입니다. 그러나 완벽한 솔루션은 없습니다. 문제를 발견하시면 저에게 답장을 보내주세요.
python.org에 토론을 쓰는 것은 이번이 처음입니다. 문제가 있으면 알려주시기 바랍니다. 감사합니다.
내 Github: https://github.com/mengqinyuan
내 Dev.to: https://dev.to/mengqinyuan
위 내용은 `shutil.copytree` 속도를 높이세요!의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!