이것은 에 대한 토론입니다. 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.33s
copy3 비용은 111.27초
비동기 버전이 싱글 스레드 버전보다 빠른 것을 확인할 수 있습니다. 그러나 단일 스레드 버전은 다중 스레드 버전보다 빠릅니다. (내 테스트 환경이 좋지 않을 수도 있으니 결과를 답장으로 보내주세요.)
베리 스캇 감사합니다!
비동기는 좋은 선택입니다. 그러나 완벽한 솔루션은 없습니다. 문제를 발견하시면 저에게 답장을 보내주세요.
python.org에 토론을 쓰는 것은 이번이 처음입니다. 문제가 있으면 알려주시기 바랍니다. 감사합니다.
내 Github: https://github.com/mengqinyuan
내 Dev.to: https://dev.to/mengqinyuan
부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.
Copyright© 2022 湘ICP备2022001581号-3