Как добиться хорошей производительности одновременного чтения с диска

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

У нас есть два больших файла, которые мы хотели бы читать из двух отдельных потоков одновременно. Один поток будет последовательно читать fileA, а другой поток будет последовательно читать fileB. Между потоками нет блокировки или связи, оба последовательно читают с максимальной скоростью, и оба немедленно отбрасывают прочитанные данные.

Наш опыт работы с этой установкой в ​​Windows очень плох. Суммарная пропускная способность двух потоков составляет порядка 2–3 МБ / с. Диск, кажется, тратит большую часть своего времени на поиск взад и вперед между двумя файлами, по-видимому, очень мало читая после каждого поиска.

Если мы отключим один из потоков и временно посмотрим на производительность одного потока, мы получим гораздо лучшую пропускную способность (~ 45 Мбайт / сек для этой машины). Итак, очевидно, что плохая двухпотоковая производительность является артефактом дискового планировщика ОС.

Что мы можем сделать для повышения производительности чтения параллельных потоков? Возможно, используя другие API или каким-либо образом настраивая параметры планировщика дисков ОС.

Некоторые детали:

Размер файлов составляет 2 ГиБ каждый на машине с 2 ГБ ОЗУ. Для целей этого вопроса мы считаем, что они не кэшируются и полностью дефрагментированы. Мы использовали инструменты дефрагментации и перезагрузились, чтобы убедиться, что это так.

Мы не используем никаких специальных API для чтения этих файлов. Поведение повторяется в различных стандартных API-интерфейсах, таких как Win32 CreateFile, C fopen, C++ std::ifstream, Java FileInputStream и т. Д.

Каждый поток вращается в цикле, вызывая функцию чтения. Мы варьировали количество байтов, запрашиваемых у API на каждой итерации, от 1 КБ до 128 МБ. Изменение этого значения не имело никакого эффекта, поэтому очевидно, что объем, который ОС физически считывает после каждого поиска диска, не определяется этим числом. Этого и следовало ожидать.

Резкое различие между однопоточным и двухпоточным производительностью сохраняется в Windows 2000, Windows XP (32-битной и 64-битной), Windows Server 2003, а также с аппаратным RAID5 и без него.

Ответов (6)

Решение

Проблема, похоже, заключается в политике планирования ввода-вывода Windows. Судя по тому, что я здесь обнаружил , у ОС есть много способов планировать запросы к диску. В то время как Linux и другие могут выбирать между различными политиками, до Vista Windows была заблокирована в одной политике: очереди FIFO, где все запросы были разделены на блоки по 64 КБ. Я считаю, что эта политика является причиной проблемы, с которой вы столкнулись: планировщик будет смешивать запросы из двух потоков, вызывая непрерывный поиск между разными областями диска.
Итак, хорошая новость заключается в том, что, согласно здесь и здесь , Vista представила более умный планировщик дисков, в котором вы можете установить приоритет своих запросов, а также выделить минимальную пропускную способность для вашего процесса.
Плохая новость в том, что я не нашел способа изменить политику диска или размер буферов в предыдущих версиях Windows. Кроме того, даже если повышение приоритета дискового ввода-вывода вашего процесса повысит производительность по сравнению с другими процессами, у вас все равно будут проблемы, связанные с конкуренцией ваших потоков друг с другом.
Что я могу предложить, так это изменить ваше программное обеспечение, введя самодельную политику доступа к диску.
Например, вы можете использовать такую ​​политику в своем потоке B (аналогично потоку A):

if THREAD A is reading from disk then wait for THREAD A to stop reading or wait for X ms
Read for X ms (or Y MB)
Stop reading and check status of thread A again  

Вы можете использовать семафоры для проверки статуса или вы можете использовать счетчики perfmon для получения статуса фактической дисковой очереди. Значения X и / или Y также можно настраивать автоматически, проверяя фактические скорости передачи и медленно их изменяя, тем самым максимизируя пропускную способность, когда приложение работает на разных машинах и / или ОС. Вы можете найти эти уровни кеша, памяти или RAID. влиять на них тем или иным образом, но с автонастройкой вы всегда добьетесь максимальной производительности в каждом сценарии.

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

Я хотел бы добавить еще несколько примечаний в свой ответ. Все остальные протестированные нами операционные системы сторонних производителей не страдают этой проблемой. Linux, FreeBSD и Mac OS X (последняя версия на другом оборудовании) ухудшаются гораздо более изящно с точки зрения совокупной пропускной способности при переходе от одного потока к двум. Linux, например, снизился с ~ 45 МБ / сек до ~ 42 МБ / сек. Эти другие операционные системы должны читать большие куски файла между каждым поиском и, следовательно, не тратить почти все свое время на ожидание на диске для поиска.

Наше решение для Windows - передать FILE_FLAG_NO_BUFFERING флаг CreateFile и использовать большие (~ 16 МБ) операции чтения при каждом вызове ReadFile . Это неоптимально по нескольким причинам:

  • Файлы не кэшируются при таком чтении, поэтому нет никаких преимуществ, которые обычно дает кеширование.
  • Ограничения при работе с этим флагом намного сложнее обычного чтения (выравнивание буферов чтения по границам страницы и т. Д.).

(В качестве заключительного замечания. Объясняет ли это, почему подкачка под Windows такая адская? Т.е. Windows неспособна выполнять ввод-вывод для нескольких файлов одновременно с какой-либо эффективностью, поэтому при замене все другие операции ввода-вывода вынуждены быть непропорционально медленными.)


Отредактируйте, чтобы добавить дополнительные сведения о Уилле Дине:

Конечно, в разных аппаратных конфигурациях исходные цифры действительно менялись (иногда существенно). Однако проблема заключается в последовательном снижении производительности, которое испытывает только Windows при переходе от одного потока к двум. Вот краткое изложение протестированных машин:

  • Несколько рабочих станций Dell (Intel Xeon) разного возраста под управлением Windows 2000, Windows XP (32-разрядная версия) и Windows XP (64-разрядная версия) с одним диском.
  • Сервер Dell 1U (Intel Xeon) под управлением Windows Server 2003 (64-разрядная версия) с RAID 1 + 0.
  • Рабочая станция HP (AMD Opteron) с Windows XP (64-разрядная версия) и Windows Server 2003 и аппаратным RAID 5.
  • Мой домашний компьютер без бренда (AMD Athlon64) под управлением Windows XP (32-разрядная версия), FreeBSD (64-разрядная версия) и Linux (64-разрядная версия) с одним диском.
  • Мой домашний MacBook (Intel Core1) под управлением Mac OS X, один диск SATA.
  • Мой домашний компьютер Koolu под управлением Linux. Чрезвычайно слабая по сравнению с другими системами, но я продемонстрировал, что даже эта машина может превзойти сервер Windows с RAID5 при выполнении многопоточного чтения с диска.

Загрузка ЦП на всех этих системах была очень низкой во время тестов, и антивирус был отключен.

Я забыл упомянуть раньше, но мы также попробовали обычный Win32 CreateFile API с установленным FILE_FLAG_SEQUENTIAL_SCAN флагом. Этот флаг не устранил проблему.

Вы используете IOCompletionPorts под Windows? В Windows через C++ есть подробная глава по этому вопросу, и, как назло, она также доступна на MSDN .

Кажется немного странным, что вы не видите разницы между довольно широким спектром версий Windows и ничего между одним диском и аппаратным raid-5.

Это всего лишь «интуиция», но это заставляет меня сомневаться в том, что это действительно простая проблема поиска. Все это, кроме OS X и Raid5, пробовали на одной машине - пробовали ли вы другую машину? Во время этого теста загрузка вашего процессора практически равна нулю?

Какое самое короткое приложение, которое вы можете написать, демонстрирует эту проблему? - Мне было бы интересно здесь попробовать.

Павел - видел обновление. Очень интересно.

Было бы интересно попробовать это в Vista или Win2008, поскольку люди, кажется, сообщают о некоторых существенных улучшениях ввода-вывода в некоторых случаях.

Мое единственное предложение по поводу другого API - это попробовать сопоставление файлов памяти - вы пробовали это? К сожалению, при 2 ГБ на файл вы не сможете сопоставить несколько файлов целиком на 32-разрядной машине, а это значит, что это не так тривиально, как могло бы быть.