Во-первых, если вы хотите максимально ускорить процесс без использования потоков, чтение и отправка строки за раз могут быть довольно медленными. Python отлично справляется с буферизацией файла, чтобы дать вам строку для чтения, но затем вы отправляете крошечные 72-байтовые пакеты по сети. Вы хотите попытаться отправить по крайней мере 1,5 КБ за один раз, когда это возможно.
В идеале вы хотите использовать метод sendfile
. Python сообщит ОС, что нужно отправить весь файл через сокет любым способом, который наиболее эффективен, без какого-либо участия вашего кода. К сожалению, это не работает в Windows; если вы заботитесь об этом, вы можете перейти к собственным API 1 напрямую с помощью pywin32
или переключиться на сетевую библиотеку более высокого уровня, такую как twisted
или asyncio
.
А как насчет потоков?
Что ж, чтение строки за раз в разных потоках не очень поможет. Потоки должны читать последовательно, борясь за указатель чтения (и буфер) в объекте файла, и они, вероятно, должны записывать в сокет последовательно, и вам, вероятно, даже нужен мьютекс, чтобы убедиться, что они пишут вещи по порядку. Итак, какой бы из них ни был медленнее, все ваши потоки в конечном итоге будут ждать своего хода. 2
Кроме того, даже забывая о сокетах: параллельное чтение файла может быть быстрее в некоторых ситуациях на современном оборудовании, но в целом на самом деле намного медленнее. Представьте, что файл находится на медленном магнитном жестком диске. Один поток пытается прочитать первый блок, следующий поток пытается прочитать 64-й блок, следующий поток пытается прочитать 4-й блок ... это означает, что вы тратите больше времени на поиск головки диска назад и вперед, чем на самом деле чтение данных.
Но, если вы думаете, что можете оказаться в одной из тех ситуаций, когда параллельное чтение может помочь, вы можете попробовать это. Это не тривиально, но не так сложно.
Во-первых, вы хотите выполнить двоичное чтение фрагментов фиксированного размера. Вам нужно будет поэкспериментировать с различными размерами - может быть, 4 КБ быстрее, может быть 1 МБ ... поэтому убедитесь, что вы задаете его как константу, которую вы легко можете изменить в одном месте кода.
Далее, вы хотите иметь возможность отправлять данные как можно скорее, а не сериализовать. Это означает, что вы должны отправлять какой-то идентификатор, например смещение в файл, перед каждым фрагментом.
Функция будет выглядеть примерно так:
def sendchunk(sock, lock, file, offset):
with lock:
sock.send(struct.pack('>Q', offset)
sent = sock.sendfile(file, offset, CHUNK_SIZE)
if sent < CHUNK_SIZE:
raise OopsError(f'Only sent {sent} out of {CHUNK_SIZE} bytes')
… за исключением того, что (если ваши файлы на самом деле не кратны CHUNK_SIZE
), вам нужно решить, что вы хотите сделать для законного EOF. Возможно, передайте общий размер файла перед любым чанком и добавьте последний чанк с нулевыми байтами, чтобы получатель урезал последний чанк.
Тогда принимающая сторона может просто зациклить чтение 8 + CHUNK_SIZE байтов, распаковывая смещение, ища и записывая байты.
1. См. TransmitFile
- но чтобы использовать это, вы должны знать, как переходить между объектами socket
уровня Python и HANDLE
s уровня Win32 и т. Д .; если вы никогда этого не делали, то есть кривая обучения - и я не знаю хорошего учебника, чтобы начать вас ..
2. Если вам действительно повезло, и, скажем, чтение файлов происходит только в два раза быстрее, чем запись в сокет, вы можете получить ускорение на 33% за счет конвейерной обработки, то есть только один поток может писать одновременно, но потоки, ожидающие записи, в основном уже закончили чтение, так что, по крайней мере, вам не нужно ждать там.