Поделиться через


Асинхронный ввод-вывод диска отображается как синхронный в 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);

Примечание.

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

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

Еще одна предостережение с асинхронной операцией заключается в том, что не следует использовать структуру 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 по умолчанию это назначается только администраторам). Корпорация Майкрософт рекомендует тщательно рассмотреть последствия использования такой функции независимыми поставщиками программного обеспечения (ISV).

Cache

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

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

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

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

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

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

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

Примечание.

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

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

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

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

  • Test 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 команду. После выполнения команды необходимо перезапустить систему, чтобы запустить сбор данных.

Ссылки

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