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

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

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

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

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

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

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

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

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

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

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

6 ответов

Решение

Похоже, проблема в политике планирования ввода-вывода Windows. В соответствии с тем, что я нашел здесь, для ОС существует множество способов планирования запросов к диску. В то время как Linux и другие могут выбирать между различными политиками, до того, как Windows Vista была заблокирована в одной политике: очередь 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 и использовать большие (~16MiB) чтения в каждом вызове 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 флаг установлен. Этот флаг не решил проблему.

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

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

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

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

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

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

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

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

Другие вопросы по тегам