Сделав первую версию программки для копирования файлов и даже получив какой-то положительный результат, правда, всего лишь в одном из множества экспериментов, я захотел вывести формулу, которая показывает, во сколько раз может быть ускорено копирование файлов в многопоточном варианте.
И вот в этом месте я застрял. Потому что по всем моим рассуждениям выходило, что существенного ускорения быть не должно. И в большинстве экспериментов выходило именно так, за исключением всего лишь одного.
В общем, долго я в фоновом режиме думал на эту тему, пока меня не осенило. Так что сейчас поделюсь результатами.
Начнем с последовательного копирования при отключенном кэшировании со стороны ОС. В этом случае время копирования файлов (замечу, что пока речь про огромные файлы) будет наибольшим и составит
где D – объем копируемых данных, Vr – скорость чтения, Vw – скорость записи данных на соответствующих дисковых устройствах.
При параллельном копировании с использованием многопоточной архитектуры время копирования составит
где Vmin – минимальная из скоростей чтения или записи. Я считаю, что в этом случае время на переключение потоков можно не учитывать в расчетах, так как оно пренебрежимо мало из-за гораздо более высокой производительности процессора и ОЗУ по сравнению с дисковыми устройствами.
Из приведенной формулы понятно, что скорость параллельного копирования существенно превышает скорость последовательного.
Но это происходит ровно до того момента, пока в работу не вступает системный файловый кэш на уровне ОС. Такой кэш реализован в виде отдельного системного процесса и работает всегда независимо и параллельно пользовательскому процессу.
Фактически это приводит к тому, что последовательное копирование при наличии файлового кэша по факту превращается в параллельное и примерно соответствует ему по скорости.
Но всегда ли?
Давайте рассмотрим процесс копирования подробнее с разных сторон.
В том случае, когда скорость чтения с диска-источника меньше скорости записи, то именно она будет лимитирующим фактором и будет однозначно определять скорость копирования как при параллельном, так и при последовательном копировании с кэшем. В этом случае никакого преимущества у параллельного копирования нет (точнее, почти нет).
В случае, когда лимитирующим фактором является запись на диск назначения и размер буфера записи в системе кэширования достаточен, то мы опять же получаем (примерный) паритет по скорости копирования у обоих вариантов.
А вот когда буфера записи становится недостаточно, то есть когда объем копируемых данных во много раз превышает размер кэша, то псевдопараллельный режим переходит в чисто последовательный и скорость записи существенно падает.
Это связано с тем, что при заполнении буфера записи при запросе приложения на запись очередной порции данных оно вынуждено ждать, пока буфер записи не скинет самую старую порцию на диск и не освободит место в ОЗУ под новую.
Таким образом, один из двух случай, когда параллельное копирование может быть существенно быстрее, чем последовательное с использованием кэширования, возникает тогда, когда объем копируемых данных существенно превышает объем кэша и копирование производится с более быстрого устройства на более медленное. Точнее, объем копируемых данных должен превышать половину кэша, так как из всего доступного объема половина будет использоваться под чтение.
Впрочем, это ограничение на половину объема можно снизить, правильно указав флаги использования копируемых файлов.
Если отбросить простые алгебраические манипуляции, то в этом случае время копирования файлов составит
где B – размер буфера записи, k – коэффициент > 1, показывающий, во сколько раз скорость чтения больше скорости записи, то есть Vr = k*Vw.
Отсюда легко получаем ускорение параллельного копирования по отношения к последовательному с кэшем
Остался еще один небольшой нюанс, связанный с переходом от копирования очередного файла к следующему. Если в случае параллельного копирования никаких пауз в этот момент нет, то вот в случае последовательного, даже с использованием кэша, она все же возникает, потому что пока запись очередного файла не закончится до конца, то есть все данные не будут сброшены на устройство, последовательное приложение не может начать копировать следующий файл.
Но насколько велика такая задержка? Приблизительно её можно оценить так
где С – размер блока read-ahead в системе файлового кэширования.
Насколько же она велика? Это зависит от размера блока и может сильно отличаться в разных версиях и вариантах системы кэширования. В простейших случаях она имеет фиксированный размер и, например, для какой-то (уже не помню номер) версии Microsoft Windows Server составляет 256 КиБ.
В любом случае можно констатировать, что она не очень велика, что подтверждается и моим тестами. Например, когда скорость дисков источника и назначения сравнима, то выигрыш параллельного копирования очень невелик и примерно соответствует приведенной величине задержке, умноженной на количество копируемых файлов за вычетом единицы.
Поэтому в том случае, когда мы копируем небольшое количество значительных по размеру файлов, такую задержку можно не учитывать, как и было в расчета времени копирования выше.
Но только не тогда, когда мы копируем очень много мелких файлов. И это второй случай, когда параллельное копирование будет значительно опережать последовательное с кэшем.
К сожалению, пока моя текущая версия программы во втором случае не может особо помочь. Это связано с особенностями работы с файлам в режиме с отключенным кэшем: порции данных, которыми ведется такая работа, должна быть кратна размеру сектора устройства.
Для оптимизации работы с большими файлами я сейчас использую достаточно большой размер такой порции.
При записи концовки большого файла или всего крохотного файла сначала записывается полная порция данных, гораздо больше, чем нужно, а файл потом усекается до требуемого размера.
В принципе, для оптимизации нужно внести совсем немного исправлений, в следующей версии, я думаю, они уже появятся.
Ну а пока, в текущей версии, сделал немного более информативный интерфейс, а то он был совсем уже слепой, да пару косячков исправил.
Кстати, для копирования файлов в параллельном режиме без использования кэширования вполне достаточно очень небольшого буфера. Его размер примерно можно определить по скорости работы накопителей. Точнее это можно сделать по IOPS, но сути это не меняет.
Сейчас я, например, исхожу из того, что размер буфера должен быть равен сумме объемов данных всех устройств, задействованных в копировании, которые они могут передать за один цикл переключения потоков. В Windows он скорее всего равен 55 мс, но может быть и меньше.
Этого хватает с большим запасом для поддержания стабильной скорости, при этом объём памяти, занимаемой программой, не превышает 25 МиБ. Потенциально может иметь важное значение в тяжело нагруженных системах, интенсивно работающих с ОЗУ.





