OverlappedIO에서 소켓 버퍼 사이즈를 바꾸는 것은 대부분 성능에 영향이 없다.

https://www.codentalks.com/t/topic/6359/13 에서 zero copy에 대한 이야기가 많이 나왔고, 저 역시 크게 생각 안하고 있다가 다시 정리하게 되었습니다.

결론만 말하자면, OverlappedIO에서 소켓 버퍼 사이즈를 바꾸는 것은 대부분 성능에 영향이 없습니다.

=============================================================================
윈도우즈 소켓 함수인 send()는 데이터의 전송을 보장하지 않는다. 정확히는 유저 버퍼에 있는 데이터를 소켓의 송신 버퍼에 복사를 해줄 뿐입니다. 데이터 전송은 운영체제에서 알아서 해준다. 그럴 수밖에 없는 것이, TCP 같은 경우는 congestion control, flow control 같은 전송량 제어 프로토콜을 따라야 하기 때문이다.

OverlappedIO을 이용하여 콜백 함수(OverlappedIO Completion Routine)나 IOCP 큐로 통지를 받은 것은 유저 버퍼의 데이터가 소켓의 송신버퍼에 복사된 것을 의미한다. 이 경우엔 All or Nothing으로 요청한 데이터 사이즈만큼 복사가 완료될 때까지 성공 이벤트가 뜨지 않는다.

그런데, 예외적인 동작이 하나 있다. 그것은 바로 SO_SNDBUF의 사이즈를 0으로 만드는 것이다. 소켓의 송신 버퍼의 사이즈를 0으로 만드는 것을 의미한다.

In most cases, the send completion in the application only indicates the data buffer in an application send call is copied to the Winsock kernel buffer and doesn’t indicate that the data has hit the network medium. The only exception is when you disable the Winsock buffering by setting SO_SNDBUF to 0.

소켓의 송신 버퍼 사이즈를 0으로 한다면, send가 완료되었다는 것(IOCP 큐로 받든, OverlappedIO Completion Routine으로 받든 혹은 그냥 send를 호출해서 IO_Pending이 아니라 성공을 의미하는 0을 받는경우)이 곧 네트워크로 패킷이 나갔다는 것을 의미한다. 나갔다는 의미가 곧 상대방이 패킷을 받았다는 것을 의미하진 않는다. 운영체제가 인식하는 수준에서 케이블 또는 와이파이를 타고 목적지를 가기 위한 다음 경유지로 패킷이 나갔음을 의미하는 것이다.

여기까지는 송신에 대한 정리이고, 수신은 딱히 특별한 무엇은 없다.

그리고, zero copy에 대한 내용이 IOCP와 OverlappedIO를 거론한 글에서 많이 나오곤 한다.


Overlapped I/O and Event Objects - Win32 apps
Windows Sockets 2 supports overlapped I/O and all transport providers support this capability.
docs.microsoft.com

위 아티클에 의하면,

수신의 경우, 수신 버퍼의 사이즈가 어떻게 되든 상관 없이 WSARecv를 통하여 제공한 버퍼에 복사된다.

For receiving, applications use the WSARecv or WSARecvFrom f unctions to supply buffers into which data is to be received. If one or more buffers are posted prior to the time when data has been received by the network, that data could be placed in the user’s buffers immediately as it arrives. Thus, it can avoid the copy operation that would otherwise occur at the time the recv or recvfrom function is invoked. If data is already present when receive buffers are posted, it is copied immediately into the user’s buffers.

그렇다면, 수신 버퍼의 사이즈를 0으로 만들어서 수신 버퍼의 zero copy를 늘리라는 기존의 이야기는 틀린 이야기란 말인가? 그것은 아니다. 정확히 말하자면(https://groups.google.com/g/microsoft.public.platformsdk.networking/c/0RZYJpWLKnw?pli=1 링크 참고),

If you set the SO_RCVBUF to 0, you want to make sure you have multiple
overlapped recv posted. This statement is valid for NT4 and Win2K. But
setting SO_RCVBUF to 0 is no longer necessary on Win2K because Win2K is
smart enough to copy data directly to your recv buffer. The Q181611 was
written in NT4 time frame and is a bit outdated. Anthony and Amol’s
article was written after Win2K was shipped.

과거의 커널에서(2001년 이전… 이메일이 2001년 이메일이다 -_-…) 의미 있는 이야기였지만, 현재(이메일 기준으로 2001년.) 기준으로는 커널에서 똑똑하게 처리해준다는 것이다.

그러면, 송신 버퍼의 사이즈를 0으로 하는 것은 제로 카피를 의미하는가? 사실 이부분은 확신이 안 든다. 다만 아래 인용을 보건대, 송신 영역에서의 zero copy는 소켓의 송신 버퍼를 완전히 대체하는 것이 아닌가 싶긴 하다. SO_SNDBUF가 기본 8kb인 경우에, 송신 완료가 의미하는 것은 유저 버퍼에서 소켓 송신 버퍼로 복사가 완료된 것을 의미하기 때문이다. SO_SNDBUF로 사이즈를 0으로 만든다는 것은 곧 유저 버퍼에서 소켓 송신 버퍼로의 복사가 완료되는 것을 송신 완료의 신호로 받을 수 없다는 것을 의미한다. 따라서, SO_SNDBUF 사이즈 0으로 바꿀 경우에, 송신 완료 신호가 의미하는 것은 송신 패킷이 네트워크로 나갔음을 의미하게 된다.

Unless you want to guarantee a packet is sent on the wire when a send completion is indicated by Winsock, you should not set the SO_SNDBUF to zero.

그렇다면, 카피를 1번은 덜하게 되니까 퍼포먼스 향상이 있지 않느냐? 하면 이것도 애매하다. 왜냐하면, 이 한 번의 카피를 줄여주는게(줄지 않을지도 모른다, 내 기억으론 윈도즈 2008 서버쯔음부터 소켓의 버퍼 사이즈를 동적으로 조절하는 내용이 추가된 것이 있기 때문이다.) 퍼포먼스 향상이 있으면 있다고 이야기할 텐데, 그 부분은 빼고 예외적으로 성능 향상이 일어날 수도 있는 경우를 게시했기 때문이다.

In fact, the default 8K buffer has been heuristically determined to work well for most situations and you should not change it unless you have tested that your new Winsock buffer setting gives you better performance than the default. Also, setting SO_SNDBUF to zero is mostly beneficial for applications that do bulk data transfer . Even then, for maximum efficiency you should use it in conjunction with double buffering (more than one outstanding send at any given time) and overlapped I/O.

"more than one outstanding send at any given time" 이 의미하는 것은 송신 완료 통지를 기다리지 말고, 일정 숫자이상(테스트를 통해 최적의 횟수를 구해야함) 송신 요청(WSASend 호출…)을 계속 걸어두라는 것이라고 추측한다.

결론은… 버프 사이즈 변경하지 말고, 변경하고 싶으면 성능 테스트를 할 수 있는 환경을 갖추고 테스트를 하라는 것이다. 이 글은 과거의 포스트 및 msdn 등의 아티클을 근거로 하였다. 그러나, 이러한 성능 문제는 누구나 납득할 수 있는 테스트 환경에서 코드 및 각종 환경을 공유하여 테스트를 해야하는데, 이것까지 할 힘이 없어서 이만 마친다…

참고1), 소켓 버퍼 사이즈 말고, 유저가 제공하는 데이터 버퍼 사이즈를 0으로 잡고 수신 post를 걸어버리면, 메모리 효율성이 좋아진다. 많이 추천하는 방식중에 하나이다.

참고2) 리눅스 역시 찾아봤는데, 아예 zero copy를 지원하는 api가 있었던 것을 확인했다. 링크는 못찾겠다…

4 Likes

최근 들어서 관심 가는 쪽은 오히려 golang, 서버 구조 등인데… 어쩌다보니 야밤에 정리하게 되었네요… TnT

리눅스나 윈도우나 커널은 맡겨두고, 유저레벨 관리나 잘 하면 된다는 얘기군요 ㅋㅋ

ㅋ_ㅋ 맞습니다

오옹 반가워요.
예전 글에 알림이 떠서 들어와봤는데 새로운 걸 알아내셨군요.

참고2) 리눅스 역시 찾아봤는데, 아예 zero copy를 지원하는 api가 있었던 것을 확인했다. 링크는 못찾겠다…

MSG_ZEROCOPY라고 검색하면 나올겁니다 :slight_smile: