Я провел сравнительный анализ, чтобы выработать некоторые общие рекомендации. Я тестировал около ~ 500 000 небольших (~ 14 КБ) файлов. Я думаю, что результаты должны быть одинаковыми для файлов среднего размера; но для больших файлов я подозреваю, что конкуренция за диск становится более значительной. Было бы полезно, если бы кто-то с более глубоким знанием внутреннего устройства ОС / оборудования мог дополнить этот ответ более конкретными объяснениями того, почему некоторые вещи работают быстрее, чем другие.
Я тестировал компьютер с 16 виртуальными ядрами (8 физическими) с двухканальной оперативной памятью и ядром Linux 4.18.
Увеличивает ли количество потоков скорость чтения?
Ответ положительный. Я думаю, что это может быть связано либо с 1) аппаратным ограничением пропускной способности для однопоточных приложений, либо с 2) очередью запросов к диску ОС лучше используется, когда многие потоки делают запросы. Наилучшая производительность достигается при virtual_cores*2
потоках. После этого пропускная способность медленно снижается, возможно, из-за увеличения конкуренции за диск. Если страницы кэшируются в оперативной памяти, то лучше иметь пул потоков размером virtual_cores
. Однако, если ‹ 50% страниц кэшируются (что, я думаю, является более распространенным случаем), то virtual_cores*2
подойдет.
Я думаю, что причина, по которой virtual_cores*2
лучше, чем просто virtual_cores
, заключается в том, что чтение файла также включает некоторую задержку, не связанную с диском, такую как системные вызовы, декодирование и т. д. Так что, возможно, процессор может более эффективно чередовать потоки: пока один ожидает на диске , второй может выполнять операции чтения файлов, не связанные с диском. (Может ли это быть связано с тем, что оперативная память двухканальная?)
Я протестировал чтение случайных файлов против последовательного (просматривая расположение физического блока файлов в хранилище и упорядочивая запросы по этому). Последовательный доступ дает довольно значительное улучшение с жесткими дисками, чего и следовало ожидать. Если ограничивающим фактором в вашем приложении является время чтения файла, а не обработка указанных файлов, я предлагаю вам изменить порядок запросов на последовательный доступ, чтобы получить ускорение.
![пропускная способность чтения и количество потоков](https://i.stack.imgur.com/Q1898.png)
Существует возможность использовать асинхронный дисковый ввод-вывод вместо пула потоков. Однако, судя по моим чтениям, пока нет переносимого способа сделать это (см. эту ветку Reddit). Кроме того, libuv, который поддерживает NodeJS, использует пул потоков для обработки своего файла. ИО.
Баланс между чтением и обработкой
В идеале мы могли бы иметь чтение и обработку в отдельных потоках. Пока мы обрабатываем первый файл, мы можем поставить следующий в очередь в другом потоке. Но чем больше потоков мы выделяем для чтения файлов, тем больше конфликтов процессора с потоками обработки. Решение состоит в том, чтобы предоставить более быстрой операции (чтение по сравнению с обработкой) наименьшее количество потоков, при этом обеспечивая нулевую задержку обработки между файлами. Эта формула показала хорошие результаты в моих тестах:
prop = read_time/process_time
if prop > 1:
# double virtual core count gives fastest reads, as per tests above
read_threads = virtual_cores*2
process_threads = ceil(read_threads/(2*prop))
else:
process_threads = virtual_cores
# double read thread pool so CPU can interleave better, as mentioned above
read_threads = 2*ceil(process_threads*prop)
Например: чтение = 2 с, обработка = 10 с; поэтому имейте 2 потока чтения для каждых 5 потоков обработки
В моих тестах есть только около 1-1,5% потери производительности из-за наличия дополнительных потоков чтения. В моих тестах для prop
, близкого к нулю, 1 чтение + 16 потоков обработки имели почти такую же пропускную способность, как 32 чтения + 16 потоков обработки. Современные потоки должны быть довольно легковесными, а потоки чтения в любом случае должны находиться в спящем режиме, если файлы не потребляются достаточно быстро. (То же самое должно быть верно для потоков процесса, когда prop
очень велико)
С другой стороны, слишком мало тем для чтения оказывает гораздо более значительное влияние (мой третий исходный вопрос). Например, для очень большого prop
1 чтение + 16 потоков обработки было на 36% медленнее, чем 1 чтение + 15 потоков обработки. Поскольку потоки обработки занимают все ядра тестового компьютера, поток чтения имеет слишком большую конкуренцию за ЦП и в 36% случаев не может поставить в очередь следующий файл для обработки. Итак, я рекомендую ошибиться в пользу слишком большого количества прочитанных тем. Удвоение размера пула потоков чтения, как в моей формуле выше, должно достичь этого.
Примечание. Вы можете ограничить ресурсы ЦП, потребляемые вашим приложением, задав virtual_cores
меньший процент доступных ядер. Вы также можете отказаться от удвоения, поскольку конкуренция за ЦП может быть менее серьезной проблемой, когда есть запасное ядро или больше, которое не выполняет более интенсивные потоки обработки.
Сводка
Основываясь на результатах моего теста, использование пула потоков с virtual_cores*2
потоками чтения файлов + virtual_cores
потоков обработки файлов даст вам хорошую производительность для различных сценариев синхронизации. Эта конфигурация должна дать вам примерно 2% от максимальной пропускной способности, не тратя много времени на бенчмаркинг.
15.03.2019