Windows에서 비동기 디스크 I/O가 동기로 표시됨

  • 아티클
  • 2023. 07. 17.
  •  

이 문서의 내용

  1. 요약
  2. 비동기 I/O 설정
  3. 비동기 I/O는 여전히 동기식으로 표시됩니다.
  4. 실제 테스트 결과

이 문서는 I/O의 기본 동작이 동기적이지만 비동기적으로 표시되는 문제를 해결하는 데 도움이 됩니다.

원래 제품 버전: Windows
원래 KB 번호: 156932

요약

Microsoft Windows의 파일 I/O는 동기 또는 비동기일 수 있습니다. I/O의 기본 동작은 동기식으로, I/O 함수가 호출되고 I/O가 완료되면 반환됩니다. 비동기 I/O를 사용하면 I/O 함수가 즉시 호출자에게 실행을 반환할 수 있지만 I/O는 나중에 완료될 때까지 완료된 것으로 간주되지 않습니다. 운영 체제는 I/O가 완료되면 호출자에게 알 수 있습니다. 대신, 호출자는 운영 체제의 서비스를 사용하여 미해결 I/O 작업의 상태를 확인할 수 있습니다.

비동기 I/O의 장점은 I/O 작업이 완료되는 동안 호출자가 다른 작업을 수행하거나 더 많은 요청을 발급할 시간이 있다는 것입니다. 겹치는 I/O라는 용어는 비동기 I/O 및 동기 I/O의 경우 겹치지 않는 I/O에 자주 사용됩니다. 이 문서에서는 I/O 작업에 비동기 및 동기라는 용어를 사용합니다. 이 문서에서는 판독기에서 파일 I/O 함수(예: CreateFile, ReadFileWriteFile.

비동기 I/O 작업은 종종 동기 I/O처럼 동작합니다. 이 문서에서 설명하는 특정 조건은 이후 섹션에서 설명하므로 I/O 작업이 동기적으로 완료됩니다. I/O 함수가 I/O가 완료될 때까지 반환되지 않으므로 호출자는 백그라운드 작업을 수행할 시간이 없습니다.

여러 함수는 동기 및 비동기 I/O와 관련이 있습니다. 이 문서에서는 예제로 사용합니다 ReadFileWriteFile . 좋은 대안이 될 ReadFileEx 것입니다.WriteFileEx 이 문서에서는 디스크 I/O만 구체적으로 설명하지만, 직렬 I/O 또는 네트워크 I/O와 같은 다른 유형의 I/O에 많은 원칙을 적용할 수 있습니다.

비동기 I/O 설정

FILE_FLAG_OVERLAPPED 파일을 열 때 플래그를 지정 CreateFile 해야 합니다. 이 플래그를 사용하면 파일에 대한 I/O 작업을 비동기적으로 수행할 수 있습니다. 예를 들면 다음과 같습니다.

C++
HANDLE hFile;

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

if (hFile == INVALID_HANDLE_VALUE)
      ErrorOpeningFile();

비동기 I/O를 코딩할 때는 시스템이 필요한 경우 작업을 동기식으로 만들 수 있는 권한이 있으므로 주의해야 합니다. 따라서 동기적으로 또는 비동기적으로 완료될 수 있는 I/O 작업을 올바르게 처리하는 프로그램을 작성하는 것이 가장 좋습니다. 샘플 코드는 이 고려 사항을 보여 줍니다.

비동기 작업이 완료되기를 기다리는 동안 프로그램에서 수행할 수 있는 많은 작업(예: 추가 작업 큐 대기 또는 백그라운드 작업 수행)이 있습니다. 예를 들어 다음 코드는 읽기 작업의 겹치고 겹치지 않는 완료를 올바르게 처리합니다. 처리 중인 I/O가 완료되기를 기다리는 것 외에는 아무 작업도 수행하지 않습니다.

C++
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 형식은 전달된 값과 GetOverlappedResult다릅니다&NumberOfBytesTransferred. 작업이 비동기화된 GetOverlappedResult 경우 작업이 완료된 후 작업에서 전송된 실제 바이트 수를 결정하는 데 사용됩니다. 전달된 ReadFile 것은 &NumberOfBytesRead 의미가 없습니다.

반면에 작업이 즉시 &NumberOfBytesRead 완료되면 읽은 바이트 수에 ReadFile 대해 전달됩니다. 이 경우 전달된 구조를 무시 OVERLAPPED 합니다. 사용하거나 WaitForSingleObject사용하지 마세요GetOverlappedResult.ReadFile

비동기 작업의 또 다른 주의 사항은 보류 중인 작업이 완료될 때까지 구조를 사용하지 OVERLAPPED 않아야 한다는 것입니다. 즉, 세 가지 미해결 I/O 작업이 있는 경우 세 OVERLAPPED 개의 구조를 사용해야 합니다. 구조를 다시 사용하는 OVERLAPPED 경우 I/O 작업에서 예측할 수 없는 결과를 받게 되며 데이터 손상이 발생할 수 있습니다. 또한 구조체를 처음 사용하거나 이전 작업이 완료된 후 다시 사용하기 OVERLAPPED 전에 왼쪽 오버 데이터가 새 작업에 영향을 주지 않도록 올바르게 초기화해야 합니다.

작업에 사용되는 데이터 버퍼에 동일한 유형의 제한이 적용됩니다. 데이터 버퍼는 해당 I/O 작업이 완료될 때까지 읽거나 쓸 수 없습니다. 버퍼를 읽거나 쓰면 오류가 발생하고 데이터가 손상될 수 있습니다.

비동기 I/O는 여전히 동기식으로 표시됩니다.

그러나 이 문서의 앞부분에 있는 지침을 따른 경우 모든 I/O 작업은 일반적으로 발급된 순서대로 동기적으로 완료되며 반환 시 FALSEGetLastError()ERROR_IO_PENDING를 반환하지 ReadFile 않습니다. 즉, 백그라운드 작업을 수행할 시간이 없습니다. 왜 이런 일이 발생합니까?

비동기 작업에 대해 코딩한 경우에도 I/O 작업이 동기적으로 완료되는 데는 여러 가지 이유가 있습니다.

압축

비동기 작업에 대한 한 가지 장애물은 NTFS(신기술 파일 시스템) 압축입니다. 파일 시스템 드라이버는 압축된 파일에 비동기적으로 액세스하지 않습니다. 대신 모든 작업이 동기식으로 만들어집니다. 이 장애는 COMPRESS 또는 PKZIP와 유사한 유틸리티로 압축된 파일에는 적용되지 않습니다.

NTFS 암호화

압축과 마찬가지로 파일 암호화로 인해 시스템 드라이버가 비동기 I/O를 동기로 변환합니다. 파일이 암호 해독되면 I/O 요청이 비동기적입니다.

파일 확장

I/O 작업이 동기적으로 완료되는 또 다른 이유는 작업 자체입니다. Windows에서 길이를 확장하는 파일에 대한 쓰기 작업은 동기식입니다.

참고

애플리케이션은 함수를 사용하여 SetFileValidData 파일의 유효한 데이터 길이를 변경한 다음 실행하여 이전에 언급한 쓰기 작업을 비동기식으로 만들 수 있습니다 WriteFile.

애플리케이션은 Windows XP 이상 버전에서 사용할 수 있는 파일을 사용하여 SetFileValidData 파일을 0으로 채우지 않으면서 성능 저하 없이 파일을 효율적으로 확장할 수 있습니다.

NTFS 파일 시스템은 데이터를 정의된 SetFileValidData유효한 VDL(데이터 길이)까지 채우지 않으므로 이 함수는 이전에 다른 파일이 차지했던 클러스터에 파일이 할당될 수 있는 보안에 영향을 줍니다. 따라서 SetFileValidData 호출자에게 새 SeManageVolumePrivilege 기능을 사용하도록 설정해야 합니다(기본적으로 관리자에게만 할당됨). MICROSOFT는 ISV(독립 소프트웨어 공급업체)가 이러한 기능을 사용할 때의 영향을 신중하게 고려하는 것이 좋습니다.

캐시

대부분의 I/O 드라이버(디스크, 통신 등)에는 I/O 요청을 즉시 완료할 수 있는 경우 작업이 완료되고 ReadFile 또는 WriteFile 함수가 TRUE를 반환하는 특수 사례 코드가 있습니다. 모든 면에서 이러한 유형의 작업은 동기식으로 표시됩니다. 디스크 디바이스의 경우 일반적으로 데이터가 메모리에 캐시될 때 I/O 요청을 즉시 완료할 수 있습니다.

데이터가 캐시에 없음

그러나 데이터가 캐시에 없는 경우 캐시 구성표가 작동할 수 있습니다. Windows 캐시는 파일 매핑을 사용하여 내부적으로 구현됩니다. Windows의 메모리 관리자는 캐시 관리자에서 사용하는 파일 매핑을 관리하는 비동기 페이지 오류 메커니즘을 제공하지 않습니다. 캐시 관리자는 요청된 페이지가 메모리에 있는지 확인할 수 있으므로 비동기 캐시된 읽기를 실행하고 페이지가 메모리에 없는 경우 파일 시스템 드라이버는 스레드가 차단되지 않도록 가정하고 제한된 작업자 스레드 풀에서 요청을 처리합니다. 읽기가 보류 중인 통화 후 ReadFile 컨트롤이 프로그램에 반환됩니다.

이 작업은 적은 수의 요청에 대해 잘 작동하지만 작업자 스레드 풀이 제한되어 있기 때문에(현재 16MB 시스템에서 3개) 특정 시간에 디스크 드라이버에 큐에 대기 중인 요청은 몇 개뿐입니다. 캐시에 없는 데이터에 대해 수많은 I/O 작업을 실행하면 캐시 관리자와 메모리 관리자가 포화 상태가 되고 요청이 동기식으로 만들어집니다.

캐시 관리자의 동작은 순차적으로 또는 임의로 파일에 액세스하는지 여부에 따라 영향을 받을 수도 있습니다. 캐시의 이점은 순차적으로 파일에 액세스할 때 가장 많이 표시됩니다. 호출의 CreateFile 플래그는 FILE_FLAG_SEQUENTIAL_SCAN 이러한 유형의 액세스에 대해 캐시를 최적화합니다. 그러나 임의 방식으로 파일에 액세스하는 경우 플래그 CreateFile 를 FILE_FLAG_RANDOM_ACCESS 사용하여 캐시 관리자에게 임의 액세스에 대한 동작을 최적화하도록 지시합니다.

캐시 사용 안 함

플래그는 FILE_FLAG_NO_BUFFERING 비동기 작업에 대한 파일 시스템의 동작에 가장 큰 영향을 미칩니다. I/O 요청이 비동기임을 보장하는 가장 좋은 방법입니다. 파일 시스템에 캐시 메커니즘을 전혀 사용하지 않도록 지시합니다.

참고

데이터 버퍼 맞춤 및 디바이스의 섹터 크기와 관련이 있는 이 플래그를 사용하는 데는 몇 가지 제한 사항이 있습니다. 자세한 내용은 이 플래그를 올바르게 사용하는 방법에 대한 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개의 I/O 요청을 신속하게 발급했으며 다른 작업을 수행하거나 더 많은 요청을 발급하는 데 많은 시간을 할애했음을 보여 줍니다.
  • 테스트 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.
    
    이 테스트는 이 프로그램이 ReadFile을 호출하여 작업을 완료하는 데 4.495880초가 소요되었지만 테스트 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 를 지정하지 않고 기본 파일 액세스는 동기 및 캐시된 작업입니다.

참고

파일 시스템 드라이버가 수정된 데이터의 예측 비동기 읽기 및 비동기 지연 쓰기를 수행하므로 이 모드에서는 일부 자동 비동기 동작이 수행됩니다. 이 동작은 애플리케이션의 I/O를 비동기화하지는 않지만 대부분의 간단한 애플리케이션에 적합합니다.

반면, 애플리케이션이 간단하지 않은 경우 이 문서의 앞부분에서 설명한 테스트와 유사하게 최상의 방법을 결정하기 위해 몇 가지 프로파일링 및 성능 모니터링을 수행해야 할 수 있습니다. 또는 WriteFile 함수에서 ReadFile 소요된 시간을 프로파일링한 다음, 실제 I/O 작업을 완료하는 데 걸리는 시간과 이 시간을 비교하는 것이 유용합니다. 대부분의 시간이 실제로 I/O를 실행하는 데 소요되는 경우 I/O가 동기적으로 완료됩니다. 그러나 I/O 요청을 실행하는 데 소요된 시간이 I/O 작업을 완료하는 데 걸리는 시간에 비해 상대적으로 작으면 작업이 비동기적으로 처리됩니다. 이 문서의 앞부분에서 언급한 샘플 코드는 함수를 QueryPerformanceCounter 사용하여 자체 내부 프로파일링을 수행합니다.

성능 모니터링은 프로그램에서 디스크 및 캐시를 얼마나 효율적으로 사용하는지를 결정하는 데 도움이 될 수 있습니다. Cache 개체에 대한 성능 카운터를 추적하면 캐시 관리자의 성능이 표시됩니다. 실제 디스크 또는 논리 디스크 개체의 성능 카운터를 추적하면 디스크 시스템의 성능이 표시됩니다.

성능 모니터링에 유용한 몇 가지 유틸리티가 있습니다. PerfMon 특히 DiskPerf 유용합니다. 시스템에서 디스크 시스템의 성능에 대한 데이터를 수집하려면 먼저 명령을 실행 DiskPerf 해야 합니다. 명령을 실행한 후 시스템을 다시 시작하여 데이터 수집을 시작해야 합니다.

참조

동기 및 비동기 I/O

Posted by gurupia
,