Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C# 고성능 서버 - System.IO.Pipeline 도입 후기 | leafbird/devnote #6

Open
leafbird opened this issue Dec 29, 2020 · 3 comments

Comments

@leafbird
Copy link
Owner

https://leafbird.github.io/devnote/2020/12/27/C-%EA%B3%A0%EC%84%B1%EB%8A%A5-%EC%84%9C%EB%B2%84-System-IO-Pipeline-%EB%8F%84%EC%9E%85-%ED%9B%84%EA%B8%B0/

들어가며2018년에 네트워크 레이어 성능을 끌어올리기 위해 도입했던 System.IO.Pipeline을 간단히 소개하고, 도입 후기를 적어본다. 윈도우 OS에서 고성능을 내기 위한 소켓 프로그래밍을 할 때 IOCP 의 사용은 오래도록 변하지 않는 정답의 자리를 유지하고 있다. 여기에서 좀 더 성능에 욕심을 내고자 한다면 Windows Server 20

@stjeong
Copy link

stjeong commented Jan 11, 2021

글 너무 잘 읽었습니다. ^^ 그런데... 사실 Pipe를 사용하는 것과 Task 2개 생성되는 것은 별개의 문제가 아닐까요? MSDN docs 예제의 경우 Read/Write 각각 비동기로 Pipe를 쓰도록 만들어서 Task가 2개 생성된 것일 뿐, 그것이 파이프라인처럼 쓸 수 있는 라이브러리라는 성격과는 별 상관이 없습니다. 바꾸신 ZeroCopyBuffer를 활용해도 마찬가지로 (msdn docs 예제처럼) Read/Write를 각각 비동기로 동시에 처리해야 한다면 Task가 2개 생성되었을 것으로 보입니다.

실제로 다음의 예제는,

https://github.com/davidfowl/TcpEcho/

Pipe를 사용하지만 read/write를 동시에 하지는 않으므로 Task 1개로 처리된 유형입니다.

@leafbird
Copy link
Owner Author

@stjeong
글 너무 잘 읽었습니다. ^^ 그런데... 사실 Pipe를 사용하는 것과 Task 2개 생성되는 것은 별개의 문제가 아닐까요? MSDN docs 예제의 경우 Read/Write 각각 비동기로 Pipe를 쓰도록 만들어서 Task가 2개 생성된 것일 뿐, 그것이 파이프라인처럼 쓸 수 있는 라이브러리라는 성격과는 별 상관이 없습니다. 바꾸신 ZeroCopyBuffer를 활용해도 마찬가지로 (msdn docs 예제처럼) Read/Write를 각각 비동기로 동시에 처리해야 한다면 Task가 2개 생성되었을 것으로 보입니다.

실제로 다음의 예제는,

https://github.com/davidfowl/TcpEcho/

Pipe를 사용하지만 read/write를 동시에 하지는 않으므로 Task 1개로 처리된 유형입니다.

안녕하세요. 성태님이 친히 댓글도 달아주시니 영광입니다 :)

남겨주신 글을 보고 github의 코드를 보니, 제가 처음 봤을 때보다는 많이 간결해 졌네요. 말씀처럼 이제는 peer당 1개의 task만 사용하고 있기도 하고요.

제가 적은 글은 다시 읽어보면, 단점을 더 강조하고자 하는 마음에 'Task의 수'를 언급한 것 같습니다. 실행컨텍스트 전환마다 잠시간의 대기를 만드는 이 못된(?) Task를 하나도 아니고 두 개, 네 개나 쓴다니 이것은 매우 큰 단점이다 하고 말하는 것 같네요.

하지만 다음 recv 완료 통지를 받을 때까지 await으로 대기해야 하는 단 한 개의 task만 있더라도

  1. recv간의 간격(=대기시간)이 길어지고
  2. 서버에서 서비스 하고자 하는 최대 동시접속자 수의 목표치가 높고
  3. cpu가 IO만 하면 될 게 아니고 쉴틈없이 다시 수십 프레임/sec 의 여러가지 계산을 해야 하면서
  4. 사용자가 20msec 정도의 latency만 더 생겨도 아주 민감하게 투덜대는 서비스를 해야 한다면

이 때는 되려 Pipeline 도입 전보다도 성능이 떨어질 만큼의 피로감을 누적시키게 되더라고요. 서버형 어플리케이션이라면 모두들 다 추구하는 방향이겠지만 게임이라는 서비스의 특성상 네트워크 레이어에서 약간의 손실을 보더라도 예민하게 느끼는 점도 있을듯 합니다. 그래서 사실 task의 숫자가 중요한게 아니라, await이 아닌 방법으로 IO 완료 통지를 받는 방법이 없다는 것이 보다 본질에 가까운 단점입니다.

System.IO.Pipeline을 사용하면서 아쉬웠던 점은, 파이프라인은 정말 단순 버퍼 운용의 용도로만 사용하고, IO 통지방식은 Iocp같은 Proactor 방식을 사용하고 싶었지만 결국 그런 방법을 찾지 못한 것이었습니다. 그래서 직접 만들어 사용중인 ZeroCopyBuffer는 (protobuff의 ZeroCopyStream처럼) 딱 제가 필요한 메모리 처리만 해두고 Task를 사용하지 않는 IO 완료처리를 하고 있습니다.

그렇다고 제가 C#으로 게임서버를 만들면서 Task를 아무곳에도 안 쓰고 있느냐 하면 그렇지는 않습니다. 아주 너무 매우 많이 쓰고있죠. task 없었으면 어떻게 했을까 싶을 정도로요. 단 몇가지 내부적인 기준을 두어, 속도 혹은 성능에 민감한 부분이라면 task의 사용을 피하고 있습니다. Network IO가 이 기준상 성능에 민감한 영역이라서 코드나 구조가 좀 더 부해지더라도 C++에서 구현하던 Proactor를 사용하는 것 뿐입니다.

성태님 달아주신 댓글을 보고 제가 지금 다시 단락의 제목을 뽑는다면 '단점 : 너무 많은 Task를 생성한다.' 가 아니라 '단점 : Task를 사용하는 것 외에는 다른 IO 완료 통지방법이 없다' 정도로 하고, 단락의 내용도 좀 더 이쪽으로 정리할 것 같습니다. 글재주도 부족한데다 급하게 쓰다보니 정리가 잘 안 되어있네요.

작년 연말에 잔여 휴가 소진하면서 코로나라고 놀러갈 수도 없고 집에 콕 박힌 김에 글이나 몇 개 써보자 했는데.. 너무 힘들더라고요. 어줍잖은 요 글 두어 개 쓰는데도 아주 진땀을 뺐습니다. 새삼 기술문서 쓰시는 분들 도대체 책 한권을 어떻게 쓰시는지 너무 존경 스럽습니다.

성태님이 왕성하게 공유해주시는 많은 노하우와 저서들에서 오랜기간 많은 도움 받고 있습니다. 어설프게나마 이 자리를 빌어 늘 감사드린다는 말씀 꼭 드리고 싶습니다. 새해 복 많이 받으세요~

@wlsvy
Copy link

wlsvy commented May 27, 2024

좋은 글 감사합니다~~

디테일한 사례도 같이 알려주시니 너무 좋군요

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants