본문 바로가기
컴퓨터/Server

PAGE_LOCKING

by 김짱쌤 2015. 4. 12.

PAGE_LOCKING

완벽한 이벤트 통지모델처럼 보이는 IOCP에도 문제는 있다. 그중 하나가 바로 이번에 다룰 PAGE_LOCKING 문제이다.

PAGE_LOCKING의 원인

http://www.slideshare.net/sm9kr/windows-registered-io-rio


WSARecv/Send를 사용할때 우리가 직접 비동기 프로세서인 커널이 사용할 버퍼(WSABuf)를 할당하여 인자로 넘겨준다. 사용자인 우리는 가상메모리 수준에서만 메모리를 활용하면 되지만, 커널인 운영체제의 입장에서는 하드웨어 수준 작업을(커널 버퍼를 거치지 않고 직접 버퍼에 매핑시키는 경우에만 발생한다는 이야기도 있다) 수행하고 있기 때문에 분명한 물리 메모리 영역을 지속적으로 다룰 수 있어야한다. 일반적인 가상메모리 영역은 물리 메모리 상황에 따라서 Page 전환이 발생하여 실제 물리메모리 역역에서 배제되거나 이동될 수 있다. 비동기 작업을 진행하는 버퍼가 Page 전환이 된다면, OS에서 수행하던 작업이 제대로 이루어지지 않을 수 있다. 따라서 운영체제는 비동기 작업에 필요한 버퍼의 물리 메모리 영역에 Lock을 건다. 이것이 PAGE_LOKING이다.

PAGE_LOCKING의 문제점

Proactor 패턴에서 작업은 비동기로 미리 명령되고, 이때 작업에 사용될 버퍼도 미리 지정한다. 위의 룰대로 따라가면 이 버퍼들은 모두 PAGE_LOCKING 상태가 될 것이다. 만약 동시에 매우 많은 작업이 요청되는 경우 지나치게 많은 물리메모리 영역이 PAGE_LOCKING될 상황이 발생할 수 있다. 이러면 다른 작업에 필요한 메모리가 부족해진다. 성능 저하에서 심한경우 그냥 멈춰버릴 수도 있다!

OS를 만드신 똑똑하신 분들은 이런 문제를 예견하고 미리 PAGE_LOKING되는 메모리 용량의 제한을 걸어두었다.(최대 전체의 1/8정도) 만약 제한된 용량을 넘어가는 Proactive 요청이 발생한다면, IOCP작업은 ERROR_INSUFFICIENT_RESOURCE 에러를 뱉고 수행되지 않는다. 다른 방향의 방지책도 있다. 커널은 Non-paged pool이라는 영역을 설정하여 페이징과 상관없는 항상 가용한 메모리를 마련해둔다. 이 영역은 PAGE_LOKING과는 관계없이 동작하기 때문에 최소 커널이 멈추는 경우는 발생하지 않는다.

Zero Byte Recv를 통한 PAGE_LOKING 최소화

제한이 걸려있다해도 PAGE_LOKING은 실제 메모리를 잡아먹는데다가 시스템콜로 동작하기 때문에, 최소화하는 것이 성능향상에 도움이 된다. Zero Byte Recv는 자주 사용되는 PAGE_LOCKING 최소화 방법이다. 쓸데없이 LOCKING되는 부분을 제거한다면 효율적으로 PAGE_LOCKING 최소화할 수 있을 것이다. 하지만 위대하신 OS의 창조주들이 쓸데없는 LOCKING을 허용하셨을까? Proactor 패턴을 적용하면 실제 수행중인 명령이 아닐지라도 일단 버퍼가 있는 메모리 영역을 PAGE_LOCKING하고 본다. 그들도 인간이기에 이런 불필요한 LOCKING이 발생한다. 그러니까 우리의 새로운 방법은 실제로 수행중일때 그때부터 LOCK을 거는 것이다.

실제 수행을 시작하는 때를 감지할 수 있다면 이 문제를 해결할 수 있다. Zero Byte Recv라는 이름을 듣고 방법을 미리 캐치하는 똑똑한 분들이 있을것이라 믿는다. 비동기 작업을 명령하기 전에 먼저 0바이트를 읽는 Recv작업을 요청하는 것이다. 0바이트 읽는 작업이므로 필요한 버퍼도 0바이트, 그러므로 PAGE_LOCKING이 발생하지 않는다. 비동기 명령 프로세서를 사용하기 때문에 이 명령이 요청된 시점에는 작업이 바로 가능한지 불확실하지만, 이 명령이 완료된 시점에는 다음 작업을 바로 시작할 수 있을 가능성이 매우 높다. 그러니까 이때부터 본격적인(큰 용량의 버퍼를 사용하는) 작업을 요청하는 것이다. 이렇게 하면 최대한 쓸데없이 LOCKING된 메모리를 줄일 수 있다.

SO_RCVBUF 옵션

이 옵션은 소켓 버퍼의 크기를 설정하는 옵션이다. 이 옵션을 사용하여 버퍼 크기를 0으로 설정하는 경우, 커널이 따로 소켓버퍼(send, recv 버퍼)를 사용하지않고 직접 유저가 설정한 버퍼(메모리)에 직접 I/O를 때려박는다. 필자는 Memory-mapped file같은 느낌이라고 이해하고 있다. 0으로 설정하면 커널 버퍼를 거치지 않고 직접 유저의 버퍼에 데이터가 저장되기 때문에, 불필요해 보이는 복사가 수행되지 않아서 성능상에 이점이 있다. 위에서 살짝 언급한것처럼 가상메모리의 영역이 하드웨어 메모리 영역과 깊은 커플링을 맺게되면서, PAGE_LOCKING의 원인이 된다. 그렇다면 유저는 복사를 하지 않는 성능 이점과 PAGE_LOCKING의 이슈를 저울질 하여 옵션을 선택해야 할 것이다.