Подтвердить что ты не робот

Объяснение для крошечных чтений (перекрытие, буферизация) превосходит большие непрерывные чтения?

(извинения за несколько длинное введение)

Во время разработки приложения, которое предварительно кэширует весь большой файл ( > 400 Мбайт) в буферный кеш для ускорения фактического запуска позже, я тестировал, было ли чтение 4 МБ за один раз по-прежнему имело заметные преимущества при чтении только 1 Мб фрагментов на время. Удивительно, но небольшие запросы на самом деле оказались быстрее. Это казалось противоречивым, поэтому я провел более обширный тест.

Буферный кеш был очищен перед запуском тестов (просто для смеха, я сделал один запуск с файлом в буферах). Кэш-буфер обеспечивает до 2 ГБ/с независимо от размера запроса, хотя с удивительным +/- 30% случайная дисперсия).
Все чтения используют перекрывающийся ReadFile с тем же целевым буфером (дескриптор был открыт с помощью FILE_FLAG_OVERLAPPED и без FILE_FLAG_NO_BUFFERING). Используемый жесткий диск является несколько пожилым, но полностью функциональным, NTFS имеет размер кластера 8 КБ. Диск был дефрагментирован после первоначального запуска (6 фрагментов по сравнению с безразмерной, нулевой разницей). Для более качественных фигур я также использовал большой файл, ниже числа для чтения 1 ГБ.

Результаты были действительно удивительными:

4MB x 256    : 5ms per request,    completion 25.8s @ ~40 MB/s
1MB x 1024   : 11.7ms per request, completion 23.3s @ ~43 MB/s
32kB x 32768 : 12.6ms per request, completion 15.5s @ ~66 MB/s
16kB x 65536 : 12.8ms per request, completion 13.5s @ ~75 MB/s

Итак, это говорит о том, что подача десяти тысяч запросов на два кластера в длину лучше, чем представление нескольких сотен больших непрерывных чтений. Время отправки (время до возврата ReadFile) существенно возрастает по мере увеличения количества запросов, но асинхронное время завершения составляет почти половину.
Время ядра процессора составляет около 5-6% в каждом случае (на четырехъядерном процессоре, поэтому нужно действительно сказать 20-30%), в то время как асинхронные чтения завершаются, что представляет собой удивительный объем процессора - видимо, невыносимое количество занятого ожидания тоже. 30% CPU в течение 25 секунд на частоте 2,6 ГГц, что довольно много циклов для "ничего".

Любая идея, как это можно объяснить? Может быть, у кого-то есть более глубокое понимание внутренней работы Windows, перекрывающей IO? Или есть что-то существенно неправильное в том, что вы можете использовать ReadFile для чтения мегабайта данных?

Я вижу, как планировщик ввода-вывода сможет оптимизировать несколько запросов, сводя к минимуму запросы, особенно когда запросы являются произвольным доступом (а это они не являются!). Я также вижу, как жесткий диск сможет выполнить подобную оптимизацию, учитывая несколько запросов в NCQ.
Однако мы говорим о смехотворном количестве смехотворно маленьких запросов, которые, тем не менее, превосходят то, что кажется разумным в 2 раза.

Sidenote: Явным победителем является сопоставление памяти. Я почти склонен добавить "неудивительно", потому что я большой поклонник картографирования памяти, но в этом случае меня это действительно удивляет, так как "запросы" еще меньше, а ОС должна быть еще менее способна прогнозировать и назначьте IO. Сначала я не тестировал картографирование памяти, потому что казалось противоречивым, что он мог бы конкурировать даже удаленно. Так много для вашей интуиции, хе.

Отображение/разворачивание представления несколько раз при разных смещениях занимает практически нулевое время. Использование представления 16 МБ и сбоя каждой страницы с простым циклом for(), считывающим один байт на страницу, завершается через 9.2 сек @~ 111 МБ/с. Потребление процессора составляет менее 3% (одно ядро). Тот же компьютер, тот же диск, то же самое.

Также оказывается, что Windows загружает 8 страниц в буферный кеш за раз, хотя фактически создается только одна страница. Неисправность каждой восьмой страницы работает с одинаковой скоростью и загружает один и тот же объем данных с диска, но показывает более низкие показатели "физическая память" и "системный кеш" и только 1/8 ошибок страницы. Последующие чтения доказывают, что страницы, тем не менее, окончательно находятся в буферном кеше (без задержки, без активности диска).

(Возможно, очень, очень отдаленно связанный с Файл с отображением памяти быстрее на большом последовательном чтении?)

Чтобы сделать это немного более наглядным:
enter image description here

Update:

Использование FILE_FLAG_SEQUENTIAL_SCAN кажется несколько "сбалансированным" показанием 128k, улучшая производительность на 100%. С другой стороны, это сильно влияет на чтение 512k и 256k (вам нужно задаться вопросом, почему?) И не имеет никакого реального эффекта ни на что другое. График MB/s меньших размеров блоков, возможно, кажется немного более "четным", но нет разницы во времени выполнения.

enter image description here

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

При учете фактических асинхронных или "немедленных" асинхронных чтений следует отметить, что в 256 раз, асинхронный запрос Windows выполняется асинхронно. Чем меньше размер блока, тем больше запросов обслуживается "немедленно", даже когда они недоступны сразу (т.е. ReadFile просто запускается синхронно). Я не могу разобрать четкую схему (например, "первые 100 запросов" или "более 1000 запросов" ), но, похоже, существует обратная корреляция между размером запроса и синхронностью. При блочном размере 8k каждый асинхронный запрос обслуживается синхронно.
Буферизованные синхронные передачи по какой-то причине в два раза быстрее, чем асинхронные передачи (не знаю, почему), следовательно, чем меньше размеры запросов, тем быстрее общий перенос, поскольку больше передач выполняется синхронно.

Для предварительного отображения с отображением памяти FILE_FLAG_SEQUENTIAL_SCAN вызывает несколько другую форму графика производительности (имеется "вырез", который перемещается немного назад), но общее время, проведенное, точно идентично (опять же, это удивительно, но я не может помочь).

Обновление 2:

Небуферизованный ввод-вывод делает графики производительности для 1M, 4M и 512k запросов тестовых корпусов несколько выше и более "острыми" с максимумами в 90-х годах GB/s, но с суровыми минимумами также общая продолжительность работы 1GB находится в пределах +/- 0,5 с буферизованного прогона (запросы с меньшими размерами буфера выполняются значительно быстрее, однако, потому что с более чем 2558 запросами в полете возвращается ERROR_WORKING_SET_QUOTA). Измеренное использование ЦП равно нулю во всех небуферизованных случаях, что неудивительно, поскольку любое IO, которое происходит, проходит через DMA.

Еще одно очень интересное наблюдение с FILE_FLAG_NO_BUFFERING заключается в том, что он существенно изменяет поведение API. CancelIO больше не работает, по крайней мере, не в смысле отмены IO. С небуферизованными запросами в полете CancelIO будет просто блокироваться, пока все запросы не будут завершены. Адвокат, вероятно, будет утверждать, что эта функция не может быть привлечена к ответственности за пренебрежение ее обязанностью, потому что больше нет запросов на полет, оставшихся после их возвращения, поэтому каким-то образом он сделал то, что было задано, - но мое понимание "отмены" означает, несколько отличается.
С буферизованным перекрытием IO, CancelIO будет просто вырезать веревку, все операции в полете немедленно прекращаются, как и следовало ожидать.

Еще одна забавная вещь заключается в том, что этот процесс не может быть до тех пор, пока все запросы не будут завершены или не будут выполнены. Такой подход имеет смысл, если ОС делает DMA в этом адресном пространстве, но тем не менее это потрясающая "функция".

4b9b3361

Ответ 1

Я не эксперт по файловой системе, но думаю, что здесь есть несколько вещей. Прежде всего. w.r.t. ваш комментарий о сопоставлении памяти является победителем. Это не удивительно, так как диспетчер кэширования NT основан на сопоставлении памяти - путем самоопределения памяти вы дублируете поведение менеджера кэша без дополнительных копий памяти.

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

Вы пытались добавить FILE_FLAG_SEQUENTIAL_SCAN к своим флагам CreateFile? Это предписывает prefetcher быть еще более агрессивным.

Это может быть интуитивно понятным, но традиционно самым быстрым способом чтения данных с диска является использование асинхронного ввода-вывода и FILE_FLAG_NO_BUFFERING. Когда вы это делаете, ввод-вывод идет непосредственно из драйвера диска в ваши буферы ввода-вывода, и ничто не мешает ему (при условии, что сегменты файла смежны - если это не так, файловая система должна будет выдайте несколько дисков, чтобы выполнить запрос на чтение приложения). Конечно, это также означает, что вы потеряли встроенную логику предварительной выборки и вынуждены сворачивать свои собственные. Но с FILE_FLAG_NO_BUFFERING у вас есть полный контроль над конвейером ввода-вывода.

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