글 작성자: Sowhat_93

<SO_SNDBUF 0으로 맞추는것 성능에 정말로 이득이 되는가?>

winsock을 다루는 많은 서적에서 SO_SNDBUF를 0으로 맞추면 send 시에 커널 버퍼에 카피를 하지 않기 때문에 성능에 이득이 된다고 한다.
하지만 실제로는 0로 맞춘다고 해도 복사가 일어난다.

아래의 msdn을 참고.

https://docs.microsoft.com/en-us/troubleshoot/windows/win32/data-segment-tcp-winsock

You can change the amount of Winsock kernel buffer allocated to the socket using the 
SO_SNDBUF option (it's 8K by default). If necessary, Winsock can buffer more than the SO_SNDBUF buffer size.

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.

Winsock uses the following rules to indicate a send completion to the application 
(depending on how the send is invoked, the completion notification could be the function returning from a blocking call, 
signaling an event, or calling a notification function, and so forth):

If the socket is still within SO_SNDBUF quota, 
Winsock copies the data from the application send and 
indicates the send completion to the application.

If the socket is beyond SO_SNDBUF quota and there's only one previously buffered send still in the stack kernel buffer, 
Winsock copies the data from the application send and indicates the send completion to the application.

If the socket is beyond SO_SNDBUF quota and there's more than one previously buffered send in the stack kernel buffer, 
Winsock copies the data from the application send. Winsock doesn't indicate the send completion to the application until 
the stack completes enough sends to put back the socket within SO_SNDBUF quota or only one outstanding send condition.

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. 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.

SO_SNDBUF로 윈속의 커널 버퍼 크기를 바꿀수 있다.
보통은 8K 가 기본 크기이다. (OS 버전마다 달라질수 있다.)
윈속은 필요하다면 SO_SNDBUF로 설정된 버퍼의 크기보다 더 사용할 수 있다.
(내가 0로 설정한다고 해서 무조건 0로 사용하는것이 아니라는 이야기.)  

App(응용프로그램) 단에서의 Completion (GQCS , Completion Rountine , 블로킹 소켓 send 리턴, WSASend 리턴 0 기타 등등.. )은
단지 함수 호출시 파라미터로 전달했던 유저버퍼의 내용이 커널 버퍼로 복사가 되었음을 의미한다.
보내고자하는 데이터가 실제로 목적지에 도착했음을 의미하는것이 아니라는 것이다.
이 경우 , 동작이 달라지는 경우가 한가지 있는데, SO_SNDBUF를 0로 설정했을 때 이다. 

윈속은 다음과 같은 경우에 send 완료 통지를 날린다.

1. 소켓의 송신 커널 버퍼가 충분할 경우, 소켓은 App의 버퍼를 송신 커널 버퍼로 복사하고, 
응용 프로그램 단으로 완료통지를 한다. 

2. 소켓의 송신 커널 버퍼가 충분하지 않고, 커널에 등록된 송신요청이 단 한개가 있을경우,
이 경우에도 소켓은 App의 버퍼를 송신 커널 버퍼로 복사하고, 완료 통지를 바로 한다. 
(즉 , 송신 버퍼 0으로 맞추고 Send를 1 Complete 1Post 로 하는 방식으로 만들었다면  이경우에 해당 된다.)

3. 소켓의 송신 커널 버퍼가 충분하지 않고, send명령이 1개 초과로 커널에 쌓여 있을 경우
송신버퍼가 충분해지기 전까지, 
혹은 2번의 상태 , 즉 충분한 send를 처리해서 send명령이 단 한개가
남은 상태가 되기 전까지 완료통지를 주지 않는다.

완료통지를 받을때 선으로 패킷이 나가는것을 보장받고 싶은 것이 아니라면, 
SO_SNDBUF를 0로 맞추는것은 피해야한다. 사실, 8K라는 기본 버퍼사이즈는 대부분의 상황에서
잘 동작하게 하기위해 정교하게 설정된 것이다.
임의로 바꾼 버퍼사이즈가 기본값 8K 보다 실제로 테스트를 해서 더 나은 성능을 보여주는지를 확인하지 못했다면,
송신버퍼 크기를 마음대로 바꾸는것은 가급적 피해야한다.
대부분, 송신 버퍼크기를 0으로 맞추는 것은 대용량 데이터를 전송할때 성능향상을 보여줄 것인데,
사실은 그 상황에서도 send를 여러번 호출하되 overlapped IO를 사용하는것이 성능에 이득이된다.

-> SND_BUF 바꾸지 마라!
 
<Overlapped IO 에서 RCV_BUF 0 설정 의미 있는가 ? >
마찬가지로 Copy를 줄여서 성능상의 이득을 얻겠다는 건데, 
오히려 조금 난해한 동작을 보인다.

2000년도 MS Engineer의 답변

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.

SO_RCVBUF를 0으로 설정하면, 미리 중첩 수신 IO를 걸어 두어야한다.
윈 2000 혹은 NT4에서는 이렇게 해야 한다.
그런데 윈 2000 부터는 더이상 위의 작업이 의미가 없는것이,
윈 2000부터는 유저 버퍼로 직접 복사해주는 기능이 생겼기 때문이다.

-> RCV_BUF 설정 0으로 해도 소용 없다.

MSDN 

https://docs.microsoft.com/en-us/windows/win32/winsock/overlapped-i-o-and-event-objects-2

If data arrives when no receive buffers have been posted by the application, 
the network resorts to the familiar synchronous style of operation. 
That is, the incoming data is buffered internally until the application 
issues a receive call and thereby supplies a buffer into which the data can be copied. 
An exception to this is when the application uses setsockopt to set the size of the receive buffer to zero. 
In this instance, reliable protocols would only allow data to be received when application buffers had been 
posted and data on unreliable protocols would be lost.

어플리케이션이 버퍼를 제공하지 않은 상태에서, ( 즉 Recv계열 함수를 호출하지 않은 상태를 이야기한다.) 
이 경우에는 우리가 잘 알고있는 방식으로 동작한다. 도착한 데이터가 소켓의 수신 버퍼에 쌓인다.
그리고 Recv계열 함수가 호출되면 param으로 전달된 buffer에 소켓의 수신 버퍼로부터 복사가 이루어진다.
한가지 예외가 있는데, setsockopt 으로 SO_RCVBUF를 0로 설정하게 되면, 
Reliable Protocol (TCP가 예) 의 경우 응용프로그램의 버퍼가 제공되지 않은 상태에서 들어온 패킷은 잃어 버리게 된다.
(Recv계열 함수를 한번도 호출 하지 않은 상태 혹은 이전 완료통지를 받은이후 아직 Recv Post 하지 않은 상태)  
UDP의 경우에는 Recv를 Post를 했던 안했던 관계없이 아예 잃어 버리게 된다.

-> Overlapped IO를 사용하면 보통 1 Recv를 많이 사용을하는데(보통 워커스레드에서 Recv 통지를 받은후 일련의 처리후 다시 Recv를 Post한다) 
패킷을 잃어버리기 딱 좋다.
실제로 위의 상황을 재현하면 패킷을 잃어버리지는 않는다.
하지만 msdn 을 믿는것이 좋아보인다.

요약
가급적 소켓 송수신 버퍼사이즈는 바꾸지 말아라.
- 대부분의 상황에서는 성능에 도움이 안된다. (대용량 전송에서는 도움이 될 수 있다.)
- 시도하려면 개발자가 테스트를 충분히 할 것을 권장.
- 심지어 대용량 전송을 시도하는 App의 경우에도 Overlapped IO를 이용한 다중 Send Post가 낫다.
- 수신 버퍼 사이즈 0로 세팅 하다가는 패킷 잃어버릴 수 있다