Многопоточная копия файла намного медленнее, чем один поток на многоядерном процессоре - PullRequest
3 голосов
/ 21 декабря 2011

Я пытаюсь написать многопоточную программу на Python, чтобы ускорить копирование (до 1000) .csv файлов. Многопоточный код работает даже медленнее, чем последовательный подход. Я рассчитал код с profile.py. Я уверен, что я должен делать что-то не так, но я не уверен, что.

Окружающая среда:

  • Четырехъядерный процессор.
  • 2 жестких диска, один из которых содержит исходные файлы. Другой пункт назначения.
  • 1000 CSV-файлов размером от нескольких КБ до 10 МБ.

Подход:

Я помещаю все пути к файлам в очередь и создаю 4-8 рабочих потоков, извлекаю пути к файлам из очереди и копирую указанный файл. Ни в коем случае многопоточный код не работает быстрее:

  • последовательное копирование занимает 150-160 секунд
  • многопоточное копирование занимает более 230 секунд

Я предполагаю, что это задача, связанная с вводом / выводом, поэтому многопоточность должна помочь скорости работы.

Код:

    import Queue
    import threading
    import cStringIO 
    import os
    import shutil
    import timeit  # time the code exec with gc disable
    import glob    # file wildcards list, glob.glob('*.py')
    import profile # 

    fileQueue = Queue.Queue() # global
    srcPath  = 'C:\\temp'
    destPath = 'D:\\temp'
    tcnt = 0
    ttotal = 0

    def CopyWorker():
        while True:
            fileName = fileQueue.get()
            fileQueue.task_done()
            shutil.copy(fileName, destPath)
            #tcnt += 1
            print 'copied: ', tcnt, ' of ', ttotal

    def threadWorkerCopy(fileNameList):
        print 'threadWorkerCopy: ', len(fileNameList)
        ttotal = len(fileNameList)
        for i in range(4):
            t = threading.Thread(target=CopyWorker)
            t.daemon = True
            t.start()
        for fileName in fileNameList:
            fileQueue.put(fileName)
        fileQueue.join()

    def sequentialCopy(fileNameList):
        #around 160.446 seconds, 152 seconds
        print 'sequentialCopy: ', len(fileNameList)
        cnt = 0
        ctotal = len(fileNameList)
        for fileName in fileNameList:
            shutil.copy(fileName, destPath)
            cnt += 1
            print 'copied: ', cnt, ' of ', ctotal

    def main():
        print 'this is main method'
        fileCount = 0
        fileList = glob.glob(srcPath + '\\' + '*.csv')
        #sequentialCopy(fileList)
        threadWorkerCopy(fileList)

    if __name__ == '__main__':
        profile.run('main()')

Ответы [ 5 ]

10 голосов
/ 21 декабря 2011

Конечно, это медленнее.Жесткие диски постоянно ищут между файлами.Ваша вера в то, что многопоточность сделает эту задачу быстрее, совершенно неоправданна.Ограничение скорости - это скорость, с которой вы можете читать или записывать данные на диск, и каждый поиск одного файла в другом - это потеря времени, которое могло быть потрачено на передачу данных.

1 голос
/ 06 августа 2017

Я думаю, что могу убедиться, что это ситуация с дисковым вводом / выводом.Я провел аналогичный тест на своей машине, копируя с очень быстрого сетевого сервера обратно на себя, и я увидел увеличение скорости почти в 1: 1 только при использовании вашего кода выше (4 потока).В моем тесте было скопировано 4137 файлов общим объемом 16,5 ГБ:

Sequential copy was 572.033 seconds.
Threaded (4) copy was 180.093 seconds.
Threaded (10) copy was 110.155
Threaded (20) copy was 86.745
Threaded (40) copy was 87.761

Как вы можете заметить, наблюдается некоторый «спад» при увеличении числа потоков, но при 4 потоках у меня было огромное количествоувеличение скоростиЯ работаю на ОЧЕНЬ быстром компьютере с очень быстрым сетевым подключением, поэтому я могу с уверенностью предположить, что вы достигли предела ввода-вывода.

Тем не менее, посмотрите, какой резонанс я получил здесь: Многопроцессорная / многопоточность Python для ускорения копирования файлов .У меня еще не было возможности опробовать этот код, но возможно, что Gevent мог бы быть быстрее.

  • Спенсер
1 голос
/ 21 декабря 2011

Я предполагаю, что это скорее задача, связанная с вводом / выводом, многопоточность должна помочь скорости работы, что-то не так с моим подходом ?!

Да.

  1. Слишком много знаков препинания.Только один."?"уместно.

  2. Ваше предположение неверно.Многопоточность помогает привязать процессор (иногда).Это никогда не может помочь с вводом / выводом.Никогда.

Все потоки в процессе должны ждать, пока один поток выполняет ввод / вывод.

или сопрограмму, чтобы выполнить работу ?!

Нет.

Если вы хотите выполнить много операций ввода-вывода, вам нужно много процессов.

Если вы копируете 1000 файлов, вам нужно много-много процессов.Каждый процесс копирует некоторые файлы.

0 голосов
/ 03 января 2018

Существуют cpu bounded приложения и i/o bounded приложения, обычно многопоточное приложение может получить практически линейное преимущество, если его последовательная версия ограничена процессором.Но когда вы ограничены вводом / выводом, вы ничего не получите, многие операционные системы могут показывать вам «процент занятости вашего процессора» и «процент занятости диска», так что вы можете узнать, какой у вас случай.

НО, поскольку обычно последовательный код не является асинхронным, вы заканчиваете выборку одного файла, а затем ожидаете копию этого файла, затем следующий файл.Таким образом, вы избегаете, чтобы у операционной системы был список файлов и расставляли приоритеты для запросов на чтение, основываясь на расположении диска на поверхности.

Вывод : если вы ищете максимальную производительность, переходите к однопоточному, но с использованием AsyncAPI-интерфейсы, позволяющие ОС лучше планировать запросы на чтение.

0 голосов
/ 25 июня 2012

в качестве отступления я просто хотел добавить, что приведенный выше код немного ошибочен. Вам следует позвонить fileQueue.task_done () ПОСЛЕ shutil.copy (fileName, destPath) .. в противном случае последние файлы не будут скопированы:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...