#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 заключается в том, что проблема не в загрузке, а в объединении отдельных файлов, в чем проблема