Creative Motive

IOCP Tip 본문

Network/Windows

IOCP Tip

aicosmos 2009. 2. 2. 15:31
from : http://cafe.naver.com/cyberzone/218

원문 및 퍼온 곳 : http://www.synczone.net (현재 없어졌음-20090201)에서 김준한님이 온라인 스터디 게시판에 올려놓은 글입니다.
제나름대로 보기 편하게 고친부분도 있습니다. (특히, 영어부분을 사악~ 빼버렸습니다.) 원문이 궁금하시면, 여기를 가보시길...


1. 개요


이 글은 여러분이 이미 IOCP 모델을 이해하고 있고 관련된 API들에 익숙하다는 전제 하에 진행 합니다. 만약 IOCP에 대해서 배우고 싶다면 Advanced Windows (3rd edition) by Jeffery Richter의 15장 Device I/O를 보시기 바랍니다.
IOCP는 높은 성능과 거대한 서버를 개발하기 위한 모델을 제공해 줍니다. Direct IOCP는 Winsock2에 추가되어 제공되었고, Windows NT 플랫폼에서 완전하게 수행되어집니다. 그러나, IOCP는 NT I/O 모델에서 가장 이해하고 실행하기 어려운 것 중에 하나 입니다. 여러분이 IOCP를 사용하여 더 좋은 소켓 서버를 설계할 수 있도록 돕기 위해서, 이 글에서 수 많은 팁을 제공합니다.


2. 더 많은 정보


팁 1: WriteFile, ReadFile 같은 Win32 파일 I/O 함수 대신에 WSASend, WSARecv와 같은 Winsock2 IOCP-호환 함수를 사용하라


Microsoft-based protocol providers로부터의 소켓 핸들들은 IFS 핸들들이므로 여러분은 이 핸들들을 Win32 file I/O 호출에 사용할 수 있다. 그러나, provider와 file system간에 상호작용은 많은 kernel/user 모드 트렌젝션, 쓰레드 문맥교환, 인자 직렬화( parameter Marchalling : 형이 여러 개인 인자를 배열과 같은 직렬형태로 만들어서 전송하기 편하게 만드는 기능 (역자주) ) 등과 같은 수행에 중대한 패널티를 주는 작업을 수행하게 된다. 만약, provider가 WSAPROTOCOL_INFO의 dwServiceFlags1에 XP1_IFS_HANDLES 비트를 설정하지 않았다면 ReadFile과 WriteFile에서 추가적인 인자 직렬화(marchalling)와 모드 트랜젝션이 발생한다.


주의 : 비록 ReadFile과 WriteFile이 더 많은 모드 트랜젝션을 가지지만, 위의 provider는 WSASend, WSARecv를 사용할 때 조차 피할 수 없는 추가적인 모드 트랜젝션을 가진다.


팁 2: 허용할 병렬 워커 쓰레드의 수와 생성할 워커 쓰레드의 총 수를 설정하라.


IOCP에서 사용할 워커 쓰레드의 수와 병렬 쓰레드의 수는 같은 것이 아니다. 여러분은 IOCP에 사용될 최대 2개의 병렬 쓰레드와 10개의 워커 쓰레드 풀을 가질 수 있다. IOCP에서 사용하는 병렬 쓰레드의 수와 같거나 많은 워커 쓰레드 풀을 가질 수 있으므로 큐에서 빼낸 completion packet을 관리하는 워커 쓰레드는 다른 큐에 입력된 I/O 패킷의 관리하는 지연 없이 Win32 “wait” 함수중에 하나를 호출 할 수 있다.


큐에서 빠지기 위해 대기하는 completion 패킷이 있다면 시스템은 다른 워커 쓰레드를 깨울 것이다. 결국, 첫번째 쓰레드는 Wait을 완료하고 다시 실행 되어질 수 있다. 이 일이 발생하였을 때, 실행시킬 수 있는 쓰레드의 수는 IOCP에서 허용된 병렬 쓰레드 보다 많다. ( 예를 들어, NumberOfConcurrentThreads ). 그러나, 다음 워커 쓰레드가 GetQueueCompletionStatus를 호출하고 대기 상태가 되었을 때, 시스템은 이것을 깨우지 않는다. 다시 말해서, 시스템은 여러분의 요청을 병렬 워커 쓰레드의 수 많큼 두도록 시도할 것이다.


일반적으로 IOCP를 사용하는데 있어서는 하나의 CPU당 하나의 병렬 쓰레드가 필요하다. 이렇게 실행하고 싶다면, 처음 IOCP를 생성할 때 CreateCompletionPort 호출에 NumberOfConcurrentThreads값을 0으로 만들면 된다.


팁 3: posted I/O 연산과 큐에서 나온 completion packet을 연결해서 생각하라.


GetQueuedCompletionStatus는 completion packet이 큐에서 나올 때 입출력을 위해서 completion key와 중첩 구조체를 반환한다. 여러분은 각각 해당 핸들과 해당 I/O 연산 정보를 반환 받기 위해서 이 두 가지 구조체 사용해야 할 것 이다. 여러분이 소켓을 핸들 정보를 제공하기 위해 IOCP에 등록한다면, 소켓 핸들을 completion key로 사용할 수 있다. 여러분의 응용프로그램에서 정의한 I/O 상태 정보를 포함하는 확장 중첩 구조체 I/O 연산에 제공할 수 있다. 또한, 여러분은 각각의 중첩 I/O를 위한 유일한 중첩 구조체를 제공할 수 있다. I/O가 끝날 때, 중첩 I/O 구조체를 가리키는 같은 포인터가 반환 되어 진다.


팁 4: I/O completion 패킷을 큐에 넣는 행동.


IOCP에 큐로 들어가는 I/O completion 패킷의 순서는 Winsock2 I/O 호출이 발생한 순서와 반드시 같지는 않다. 추가적으로 만약 Winsock2 I/O 호출이 SUCCESS 또는 IO_PENDING을 반환하면, 소켓 핸들이 종료되는 것과 상관없이 I/O가 완료되었을 때, completion 패킷이 IOCP의 큐에 들어갈 것임을 보장한다. 소켓 핸들을 종료한 후에 WSASend, WSASendTo, WSARecv or WSARecvFrom의 호출은 SUCCESS 또는 IO_PENDING을 제외한 코드를 반환하면서 실패할 것이고 completion 패킷도 생성하지 않을 것이다. Completion 패킷의 상태는 이전에 요청한 I/O를 위해 GetQueuedCompletionStatus에 의해 고쳐진다.


IOCP를 제거한다면, 더 이상 어떤 I/O도 IOCP로 보내지지 않는다, 왜냐하면, IOCP 핸들 자체가 무효하기 때문이다. 그러나, 시스템의 기본적인 IOCP kernel 구조체는 보내졌던 I/O들이 모두 완료될 때 까지 사라지지 않는다.


팁 5: IOCP 정리하기.


IOCP 정리를 할 때 기억해야 하는 중요한 것은 중첩 I/O를 사용할 때와 같다 : 만약 그것을 위한 I/O 아직 완료되지 않았다면, 중첩 구조체를 반환하지 말아라. HasOverlappedIoCompleted 매크로는 중첩 구조체로부너 I/O가 완료되었는지 아닌지를 알 수 있도록 해준다.


서버를 종료하기 위한 전형적인 2가지 시나리오가 있다. 첫번째는 여러분은 밖으로 드러난 I/O들의 completion 상태를 신경 쓰지 않고 가능한 빨리 종료하기를 원할 때이고, 두 번째는 여러분은 서버를 종료하길 원하고 밖으로 드러난 각각의 I/O completion 상태을 알고 싶을 때이다.


첫 번째 시나리오에서는 여러분은 워커 쓰레드에게 즉시 종료하라는 특별한 completion 패킷을 보내기위해서 PostQueueCompletionStatus( N 번, N은 워커쓰레드의 수 )를 호출할 수 있고, 모든 소켓 핸들과 할당된 중첩 구조체를 닫을 수 있고, completion port를 닫을 수 있다. 다시 말해서, 여러분은 중첩 구조체를 해제하기 전에 그것의 completion 상태를 검사하기 위해서 HasOverlappedIoCompleted를 사용할 수 있다. 만약 소켓이 닫히면, 그 소켓의 모든 드러난 I/O는 언젠가는 빠르게 완료된다.


두 번째 시나리오에서, 여러분은 모든 completion 패킷이 아마도 큐에서 빠질 수 있도록 하기 위해 워커 쓰레드의 종료를 지원 시킬 수 있다. 여러분은 모든 소켓 핸들과 IOCP를 닫음으로써 시작할 수 있다. 그러나, 여러분은 워커 쓰레드가 언제 안전하게 종료할 수 있는지를 알 수 있게 하기위해서 미해결된 I/O들의 수를 세고 있어야 한다. IOCP를 위해 크리티컬 섹션으로 보호되어지는 전역 I/O 카운터를 가지는 데에 대한 성능 저하는 생각하는 것만큼 나쁘지 않다. 왜냐하면, 활성화된 워커 쓰레드는 만약 큐에 더 많은 completion 패킷이 있다면 교체 되어지지 않기 때문이다.