"यदि कोई कर्मचारी अपना काम अच्छी तरह से करना चाहता है, तो उसे पहले अपने औजारों को तेज करना होगा।" - कन्फ्यूशियस, "द एनालेक्ट्स ऑफ कन्फ्यूशियस। लू लिंगगोंग"
मुखपृष्ठ > प्रोग्रामिंग > `shutil.copytree` को गति दें!

`shutil.copytree` को गति दें!

2024-11-04 को प्रकाशित
ब्राउज़ करें:409

Speed up `shutil.copytree` !

shutil.copytree को तेज़ करने पर चर्चा करें

यहाँ लिखें

यह एक चर्चा है, देखें: https://discuss.python.org/t/speed-up-shutil-copytree/62078। यदि आपके पास कोई विचार है, तो कृपया मुझे भेजें!

पृष्ठभूमि

श्यूटिल पायथन में एक बहुत ही उपयोगी मौडल है। आप इसे जीथब में पा सकते हैं: https://github.com/python/cpython/blob/master/Lib/shutil.py

shutil.copytree एक फ़ंक्शन है जो एक फ़ोल्डर को दूसरे फ़ोल्डर में कॉपी करता है।

इस फ़ंक्शन में, यह कॉपी करने के लिए _copytree फ़ंक्शन को कॉल करता है।

_copytree क्या करता है?

  1. निर्दिष्ट फ़ाइलों/निर्देशिकाओं को अनदेखा करना।
  2. गंतव्य निर्देशिका बनाना।
  3. प्रतीकात्मक लिंक को संभालते समय फ़ाइलों या निर्देशिकाओं की प्रतिलिपि बनाना।
  4. आने वाली त्रुटियों को एकत्रित करना और अंततः उन्हें उठाना (उदाहरण के लिए, अनुमति संबंधी समस्याएं)।
  5. स्रोत निर्देशिका के मेटाडेटा को गंतव्य निर्देशिका में दोहराया जा रहा है।

समस्याएँ

जब फ़ाइलों की संख्या बड़ी हो या फ़ाइल का आकार बड़ा हो तो कॉपीट्री की गति बहुत तेज़ नहीं होती है।

यहाँ परीक्षण करें:

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')

  • "my_shutil" शटिल का मेरा संशोधित संस्करण है।

copy1 की लागत 173.04780609999943s
कॉपी2 की लागत 155.81321870000102s

copy2,copy1 से काफी तेज है। आप कई बार चला सकते हैं।

फायदे और नुकसान

मल्टीथ्रेड का उपयोग प्रतिलिपि बनाने की प्रक्रिया को तेज़ कर सकता है। लेकिन इससे मेमोरी का उपयोग बढ़ जाएगा. लेकिन हमें कोड में मल्टीथ्रेड को दोबारा लिखने की आवश्यकता नहीं है।

Async

"बैरी स्कॉट" को धन्यवाद। मैं उनके सुझाव का पालन करूंगा :

आपको async 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.21s
कॉपी2 की लागत 244.33 सेकेंड है
कॉपी3 की लागत 111.27 सेकेंड है


आप देख सकते हैं कि एसिंक संस्करण सिंगल थ्रेड संस्करण से तेज़ है। लेकिन सिंगल थ्रेड संस्करण मल्टी-थ्रेड संस्करण की तुलना में तेज़ है। (शायद मेरा परीक्षण वातावरण बहुत अच्छा नहीं है, आप कोशिश कर सकते हैं और अपना परिणाम मुझे उत्तर के रूप में भेज सकते हैं)

धन्यवाद बैरी स्कॉट !

फायदे और नुकसान

Async एक अच्छा विकल्प है। लेकिन कोई भी समाधान पूर्ण नहीं होता. यदि आपको कोई समस्या आती है तो आप मुझे उत्तर के रूप में भेज सकते हैं।

अंत

python.org पर चर्चा लिखने का यह मेरा पहला अवसर है। यदि कोई समस्या हो तो कृपया मुझे बताएं। धन्यवाद।

मेरा जीथब: https://github.com/mengqinyuan
मेरा Dev.to: https://dev.to/mengqinyuan

विज्ञप्ति वक्तव्य यह आलेख यहां पुन: प्रस्तुत किया गया है: https://dev.to/mengqinyuan/add-multithreading-to-shutil--2lm1?1 यदि कोई उल्लंघन है, तो कृपया इसे हटाने के लिए स्टडी_गोलंग@163.com से संपर्क करें।
नवीनतम ट्यूटोरियल अधिक>

चीनी भाषा का अध्ययन करें

अस्वीकरण: उपलब्ध कराए गए सभी संसाधन आंशिक रूप से इंटरनेट से हैं। यदि आपके कॉपीराइट या अन्य अधिकारों और हितों का कोई उल्लंघन होता है, तो कृपया विस्तृत कारण बताएं और कॉपीराइट या अधिकारों और हितों का प्रमाण प्रदान करें और फिर इसे ईमेल पर भेजें: [email protected] हम इसे आपके लिए यथाशीघ्र संभालेंगे।

Copyright© 2022 湘ICP备2022001581号-3