Как мне объединить как можно больше двоичных файлов максимально эффективно?

#python #python-3.x #file #merge #multiprocessing

Вопрос:

Я пишу загрузчик с несколькими подключениями, который загружает один файл в 32 частях с помощью 32 процессов, используя multiprocess библиотеку из UQ Foundation, и я хотел бы знать наиболее эффективный способ объединения частей обратно в один файл.

Файлы представляют собой 32 непрерывных блока (почти) одинакового размера, с разницей в размере не более 1 байта, и называются в этой формуле:

 '{0}.{1}.part'.format(filepath, str(i).zfill(2))
 

filepath представляет собой str место, где должен храниться загруженный файл, включая имя и расширение. i это int значение от 0 до 32 (не включая 32), затем с добавлением нуля в 2 цифры str , чтобы избежать алфавитной сортировки в числовых строках.

Следующая работа выполняется, но выполняется медленно и требует много памяти:

 with open(filepath, 'wb') as dest:
    for file in files:
        f = open(file, 'rb')
        dest.write(f.read())
        f.close()
        os.remove(file)
 

Это немного лучше, но все равно медленно:

 BLOCKSIZE = 4096
BLOCKS = 1024
chunk = BLOCKS * BLOCKSIZE
with open(filepath, "wb") as dest:
    for file in files:
        with open(file, "rb") as f:
            data = f.read(chunk)
            while data != b'':
                dest.write(data)
                data = f.read(chunk)
        os.remove(file)
 

(На самом деле я использую вариант второго метода, используя pathlib и mmap поэтому ни один из них с предложениями, но основная идея та же).

Вместо этого я считаю, что использовать 32 подпроцесса для одновременного чтения содержимого файлов, а затем сообщать данные родительскому процессу и позволять родительскому процессу записывать данные на диск было бы лучше, у меня 4 ядра процессора, но эти процессы не будут выполнять значительные вычисления, как это можно сделать с помощью multiprocess ?


Источник и пункты назначения находятся на одном устройстве, в одной папке, устройство либо HDD, либо SSD и, предположительно, с файловой системой NTFS (у меня жесткий диск Seagate EXOS 4 ТБ с NTFS с размером блока 4 КБ).

Файлы могут быть очень большими (я собираюсь использовать его для загрузки «интерактивного цифрового искусства с авторским правом», объем которого может превышать 20 ГБ), и у меня только 16 ГБ оперативной памяти, и я не ожидаю, что у любого пользователя будет больше оперативной памяти, чем 16 ГБ, поэтому загрузка целых файлов в память невозможна.

И я использую Windows 10 21H1, и я нацелен на Windows 10.


Моя пропускная способность составляет 100 Мбит/с или 11,92 Мбит / с, и я использую VPN, потому что нахожусь в Китае.

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

Я знаю, что большинство браузеров поддерживают не более 8 подключений на загрузку, и почти все файлы загружаются с использованием одного потока, основной стимул к использованию нескольких подключений заключается не в том, что это увеличивает пропускную способность, а скорее сводит к минимуму влияние ограничения скорости, большинство серверов устанавливают квоту, которую может иметь соединение, и это ограничение часто намного меньше пропускной способности, при использовании нескольких подключений квота будет увеличена пропорционально этому множеству, и там, где я нахожусь, вы знаете, правительство активно регулирует международный трафик, если не прямо прерывает его, а VPN увеличивает задержку, следовательно, увеличивает регулирование…

Комментарии:

1. Многопроцессорная обработка потенциально будет медленнее, потому что вам придется учитывать возможные конфликты при записи в выходной файл, т. Е. Вам понадобится какой — то механизм блокировки. Я бы ожидал, что ваш первый пример будет самым быстрым из возможных. Кроме того, я не вижу, как это могло бы привести к огромным накладным расходам на память. Можете ли вы объяснить это подробнее?

2. В чем ваше узкое место, какова ваша текущая лучшая скорость, какова ваша целевая скорость?

3. Ваши исходные и целевые файлы находятся на одном устройстве или на разных? Что это за устройство(устройства)?

4. Какую скорость вы получаете, используя самый быстрый вариант в базовой ОС? (в Linux это, вероятно, было dd бы с подходящей bs настройкой)

5. Откуда вы загружаете и с какой пропускной способностью, что имеет смысл использовать 32 параллельных процесса и может превзойти SSD?

Ответ №1:

Для части слияния я бы предложил не разбивать файлы с самого начала, вы можете создать и зарезервировать большой файл размером с основной файл, а затем логически разделить его на части и назначить начальный байт для каждого потока. Например, предположим, что у вас есть файл 4 ГБ и 4 потока, первый поток начинается с байта 0, второй начинается с байта 1024^3 (1 ГБ), а третий-с 2 ГБ и так далее. таким образом, вам не придется иметь дело со слиянием файлов. Я должен также упомянуть, что в этом решении есть некоторые проблемы с синхронизацией, которые следует решить.

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

Комментарии:

1.Единица измерения СИ для гигабайт-ГБ en.wikipedia.org/wiki/Gigabyte, в то время как Гб относится к гигабитам en.wikipedia.org/wiki/Gigabit

Ответ №2:

Я провел несколько тестов, сначала вам нужно загрузить этот файл: http://ipv4.download.thinkbroadband.com/1GB.zip (прямая ссылка) используя любой менеджер загрузок, который вы используете (для этого рекомендуется не использовать браузеры), это файл, содержащий ровно 1 Гб данных мусора специально для целей тестирования, он должен иметь этот хэш:

5674e59283d95efe8c88770515a9bbc80cbb77cb67602389fd91def26d26aed2

Разделите файл на 32 части (я загрузил файл в D:downloads1GB.zip, меняйте по мере необходимости):

 from pathlib import Path                                                      

i = 0
files = []
with Path('D:/downloads/1GB.zip').open('rb') as f:                            
    while (chunk := f.read(33554432)):
        path = 'D:/1GB.zip.{0}.part'.format(str(i).zfill(2))                                        
        Path(path).write_bytes(chunk)
        files.append(path)
        i  = 1
 

Мой диск-Seagate EXOS 7E8 4 ТБ, подключен к порту SATA III 6,0 Гб/с, его файловая система-NTFS с размером кластера 4 КБ.

Я провел следующие тесты:

Способ 1:

 with Path('D:/1GB.zip').open('wb') as dest:
    for file in files:
        dest.write(Path(file).read_bytes())
 

Способ 2:

 BLOCKSIZE = 4096
BLOCKS = 1024
CHUNKSIZE = BLOCKSIZE * BLOCKS

with Path('D:/1GB.zip').open('wb') as dest:
    for file in files:
        with Path(file).open('rb') as f:
            while (segment := f.read(CHUNKSIZE)):
                dest.write(segment)
 

Оба метода дают желаемый результат:

 import hashlib
HASH = '5674e59283d95efe8c88770515a9bbc80cbb77cb67602389fd91def26d26aed2'

sha = hashlib.sha256()
with Path('D:/1GB.zip').open('rb') as f:
    while (chunk := f.read(1048576)):
        sha.update(chunk)

print(sha.hexdigest() == HASH)
 

На моей машине, использующей магию timeit, выполнение первого метода в среднем занимает около 3,25 с, при этом наблюдаемая скорость использования диска достигает макс. 320 Мбит/с.

В то время как метод 2 занимает в среднем около 1,25 с при наблюдаемой максимальной скорости 850 Мбит/с.

Теоретически SATA III имеет пропускную способность 6,0 Гб/с, что составляет 750 Мбит/с в десятичных единицах СИ, что затем составляет 715,2557373046875 Мбит/с в двоичных единицах, что затем уменьшается на кодирование 8b/10b до максимальной скорости передачи 600 МБИТ/с, которая составляет 572,20458924375 Мбит/с в двоичных единицах.

Первый метод имеет максимальную скорость записи около 320 Мбит/с со средней скоростью 315,076923 Мбит/с, в то время как второй метод имеет максимальную скорость записи около 850 Мбит/с и среднюю скорость 819,2 Мбит/с, намного превышающую теоретический предел SATA 3,0, похоже, мой жесткий диск действительно превосходит теоретическую пропускную способность SATA, и я абсолютно увеличил производительность, чем мог себе представить, кажется, я действительно достиг предела, и использование многопроцессорной обработки не поможет, но я искренне верю, что использование mmap сделает все еще быстрее.

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

Комментарии:

1. Моя интерпретация вопроса OP заключается в том, что проблема не в загрузке, а в объединении отдельных файлов, в чем проблема