Как получить хорошую производительность одновременного чтения с диска
Я хотел бы задать вопрос, а затем ответить на него своим собственным ответом, но также посмотреть, какие ответы имеют другие люди.
У нас есть два больших файла, которые мы хотели бы читать из двух отдельных потоков одновременно. Один поток будет последовательно читать файл 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.