Асинхронный дисковый ввод-вывод отображается как синхронный в Windows

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

Исходная версия продукта: Windows
Исходный номер базы знаний: 156932

Сводка

Файловый ввод-вывод в Microsoft Windows может быть синхронным или асинхронным. Поведение по умолчанию для операций ввода-вывода является синхронным, когда функция ввода-вывода вызывается и возвращается по завершении ввода-вывода. Асинхронный ввод-вывод позволяет функции ввода-вывода немедленно возвращать выполнение обратно вызывающей стороне, но предполагается, что операции ввода-вывода будут завершены до некоторого времени в будущем. Операционная система уведомляет вызывающий объект о завершении ввода-вывода. Вместо этого вызывающий объект может определить состояние невыполненных операций ввода-вывода с помощью служб операционной системы.

Преимущество асинхронного ввода-вывода заключается в том, что вызывающий объект имеет время для выполнения другой работы или выдачи дополнительных запросов во время выполнения операции ввода-вывода. Термин Перекрытие ввода-вывода часто используется для асинхронных операций ввода-вывода и неперекрывающихся операций ввода-вывода для синхронных операций ввода-вывода. В этой статье используются термины асинхронные и синхронные для операций ввода-вывода. В этой статье предполагается, что читатель знаком с функциями файлового ввода-вывода, такими как CreateFile, ReadFile, WriteFile.

Часто асинхронные операции ввода-вывода ведут себя так же, как синхронные операции ввода-вывода. Некоторые условия, которые рассматриваются в этой статье в последующих разделах, в связи с чем операции ввода-вывода выполняются синхронно. Вызывающий объект не имеет времени на фоновую работу, так как функции ввода-вывода не возвращаются до завершения ввода-вывода.

Несколько функций связаны с синхронным и асинхронным вводом-выводом. В этой статье в качестве примеров используется ReadFile и WriteFile . Хорошими альтернативами будут ReadFileEx и WriteFileEx. Хотя в этой статье рассматривается только дисковый ввод-вывод, многие принципы могут применяться к другим типам операций ввода-вывода, таким как последовательный ввод-вывод или сетевой ввод-вывод.

Настройка асинхронного ввода-вывода

Флаг FILE_FLAG_OVERLAPPED должен быть указан в CreateFile при открытии файла. Этот флаг позволяет выполнять операции ввода-вывода с файлом асинхронно. Пример:

HANDLE hFile;

hFile = CreateFile(szFileName,
                      GENERIC_READ,
                      0,
                      NULL,
                      OPEN_EXISTING,
                      FILE_FLAG_NORMAL | FILE_FLAG_OVERLAPPED,
                      NULL);

if (hFile == INVALID_HANDLE_VALUE)
      ErrorOpeningFile();

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

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

if (!ReadFile(hFile,
               pDataBuf,
               dwSizeOfBuffer,
               &NumberOfBytesRead,
               &osReadOperation )
{
   if (GetLastError() != ERROR_IO_PENDING)
   {
      // Some other error occurred while reading the file.
      ErrorReadingFile();
      ExitProcess(0);
   }
   else
      // Operation has been queued and
      // will complete in the future.
      fOverlapped = TRUE;
}
else
   // Operation has completed immediately.
   fOverlapped = FALSE;

if (fOverlapped)
{
   // Wait for the operation to complete before continuing.
   // You could do some background work if you wanted to.
   if (GetOverlappedResult( hFile,
                           &osReadOperation,
                           &NumberOfBytesTransferred,
                           TRUE))
      ReadHasCompleted(NumberOfBytesTransferred);
   else
      // Operation has completed, but it failed.
      ErrorReadingFile();
}
else
   ReadHasCompleted(NumberOfBytesRead);

Примечание.

&NumberOfBytesRead Значение , переданное в ReadFile , отличается от &NumberOfBytesTransferred переданного в GetOverlappedResult. Если операция выполнена асинхронно, то GetOverlappedResult используется для определения фактического количества байтов, передаваемых в операции после ее завершения. Переданное &NumberOfBytesRead в ReadFile не имеет смысла.

С другой стороны, если операция завершена немедленно, то &NumberOfBytesRead передача в ReadFile допустима для количества прочитанных байтов. В этом случае игнорируйте структуру, передаваемую OVERLAPPED в ReadFile; не используйте ее с GetOverlappedResult или WaitForSingleObject.

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

Тот же тип ограничения применяется к буферу данных, используемому в операции. Буфер данных не должен считываться или записываться до завершения соответствующей операции ввода-вывода; Чтение или запись буфера может привести к ошибкам и повреждению данных.

Асинхронные операции ввода-вывода по-прежнему синхронны

Однако если вы выполнили инструкции, описанные выше в этой статье, все операции ввода-вывода по-прежнему выполняются синхронно в выданном порядке, и ни одна из ReadFile операций не возвращает значение FALSE с GetLastError() возвратом ERROR_IO_PENDING, что означает, что у вас нет времени на фоновую работу. Почему это происходит?

Существует ряд причин, по которым операции ввода-вывода выполняются синхронно, даже если вы закодировали для асинхронной операции.

Сжатия

Одним из препятствий для асинхронной операции является сжатие файловой системы новой технологии (NTFS). Драйвер файловой системы не будет обращаться к сжатым файлам асинхронно; вместо этого все операции выполняются синхронно. Это препятствие не применяется к файлам, сжатым с помощью служебных программ, аналогичных COMPRESS или PKZIP.

Шифрование NTFS

Как и сжатие, шифрование файлов приводит к тому, что системный драйвер преобразует асинхронный ввод-вывод в синхронный. Если файлы расшифрованы, запросы ввода-вывода будут асинхронными.

Расширение файла

Другой причиной синхронного завершения операций ввода-вывода являются сами операции. В Windows любая операция записи в файл, который увеличивает его длину, будет синхронной.

Примечание.

Приложения могут сделать ранее упомянутую операцию записи асинхронной, изменив допустимую длину данных файла с помощью SetFileValidData функции , а затем выдав .WriteFile

С помощью SetFileValidData (доступной в Windows XP и более поздних версиях) приложения могут эффективно расширять файлы без снижения производительности при их нулевом заполнении.

Так как файловая система NTFS не заполняет данные с нуля до допустимой длины данных (VDL), определенной параметром SetFileValidData, эта функция влияет на безопасность, когда файлу могут быть назначены кластеры, которые ранее были заняты другими файлами. Поэтому требуется, SetFileValidData чтобы вызывающий объект был включен ( SeManageVolumePrivilege по умолчанию он назначается только администраторам). Корпорация Майкрософт рекомендует независимым поставщикам программного обеспечения тщательно рассмотреть последствия использования такой функции.

Кэш

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

Данные не в кэше

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

Это хорошо подходит для небольшого количества запросов, но так как пул рабочих потоков ограничен (в настоящее время три в системе размером 16 МБ), в определенный момент времени к драйверу диска будет по-прежнему помещено в очередь только несколько запросов. Если вы выполняете многочисленные операции ввода-вывода для данных, которых нет в кэше, диспетчер кэша и диспетчер памяти становятся перегруженными, а запросы выполняются синхронно.

На поведение диспетчера кэша также можно влиять в зависимости от того, обращаетесь ли вы к файлу последовательно или случайно. Преимущества кэша чаще всего видны при последовательном доступе к файлам. Флаг FILE_FLAG_SEQUENTIAL_SCAN в вызове CreateFile оптимизирует кэш для этого типа доступа. Однако при случайном доступе к файлам используйте FILE_FLAG_RANDOM_ACCESS флаг в CreateFile , чтобы указать диспетчеру кэша оптимизировать свое поведение для случайного доступа.

Не используйте кэш

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

Примечание.

Существуют некоторые ограничения на использование этого флага, которые связаны с выравниванием буфера данных и размером сектора устройства. Дополнительные сведения см. в справочнике по функциям в документации по функции CreateFile о правильном использовании этого флага.

Результаты реальных тестов

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

Вы можете увидеть результаты, аналогичные одному из следующих:

  • Тест 1

    Asynchronous, unbuffered I/O:  asynchio /f*.dat /n
    Operations completed out of the order in which they were requested.
       500 requests queued in 0.224264 second.
       500 requests completed in 4.982481 seconds.
    

    Этот тест показывает, что ранее упомянутая программа быстро выдала 500 запросов ввода-вывода и имела много времени для выполнения других работ или выдачи дополнительных запросов.

  • Тест 2

    Synchronous, unbuffered I/O: asynchio /f*.dat /s /n
        Operations completed in the order issued.
        500 requests queued and completed in 4.495806 seconds.
    

    Этот тест показывает, что эта программа потратила 4,495880 секунды на вызов ReadFile для выполнения своих операций, но тест 1 потратил только 0,224264 секунды на выдачу одинаковых запросов. В тесте 2 у программы не было дополнительного времени для выполнения каких-либо фоновых работ.

  • Тест 3

    Asynchronous, buffered I/O: asynchio /f*.dat
        Operations completed in the order issued.
        500 requests issued and completed in 0.251670 second.
    

    Этот тест демонстрирует синхронный характер кэша. Все операции чтения были выданы и завершены в 0,251670 секунды. Другими словами, асинхронные запросы выполнялись синхронно. Этот тест также демонстрирует высокую производительность диспетчера кэша, когда данные содержатся в кэше.

  • Тест 4

    Synchronous, buffered I/O: asynchio /f*.dat /s
        Operations completed in the order issued.
        500 requests and completed in 0.217011 seconds.
    

    Этот тест демонстрирует те же результаты, что и в тесте 3. Синхронные операции чтения из кэша выполняются немного быстрее, чем асинхронные операции чтения из кэша. Этот тест также демонстрирует высокую производительность диспетчера кэша, когда данные содержатся в кэше.

Заключение

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

Доступ к файлам по умолчанию без указания специальных флагов является CreateFile синхронной и кэшированной операцией.

Примечание.

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

С другой стороны, если приложение не просто, может потребоваться профилирование и мониторинг производительности, чтобы определить лучший метод, аналогичный тестам, показанным ранее в этой статье. Полезно профилировать время, затраченное ReadFile на функцию или, WriteFile а затем сравнить это время с тем, сколько времени занимает выполнение фактических операций ввода-вывода. Если большая часть времени тратится на фактические выдачи операций ввода-вывода, то операции ввода-вывода выполняются синхронно. Однако если время, затраченное на выдачу запросов ввода-вывода, относительно невелико по сравнению с временем, затраченным на выполнение операций ввода-вывода, то ваши операции обрабатываются асинхронно. В примере кода, упомянутом ранее в этой статье, QueryPerformanceCounter функция используется для выполнения собственного внутреннего профилирования.

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

Существует несколько служебных программ, которые полезны для мониторинга производительности. PerfMon и DiskPerf особенно полезны. Чтобы система собирала данные о производительности дисковых систем, сначала необходимо выполнить DiskPerf команду . После выполнения команды необходимо перезапустить систему, чтобы начать сбор данных.

Ссылки

Синхронный и асинхронный ввод-вывод