30P by xguru 10달전 | favorite | 댓글 9개
  • 비동기 와 멀티쓰레드 간의 메모리 사용량을 Rust, Go, Java, C#, Python, Node.js, Elixir 언어로 비교
  • 10초간 기다리는 태스크를 N개 실행하는 프로그램을 각 언어로 작성(ChatGPT의 도움을 받아)
  • Xeon E3 + Ubuntu 22.04 에서 비교

결과

  • 최소 풋프린트 (1개의 태스크만 실험): Go 와 Rust는 3MB 이하만 요구, Python은 17MB, Java/Node.js는 약 40MB, C#은 131MB
  • 1만개 태스크 : Rust Tokio 4.6MB, Rust async-std 8MB, Go 28.6MB, Python 40MB, Rust Threads 48MB, Node.js 48MB, Java Virtual Thread 78MB, Elixir 99MB, C# 131MB, Java Threads 244MB
  • 10만개 태스크(쓰레드는 제외): Rust tokio 23MB, Rust Async-std 54MB, Node.js 112MB, C# 130MB, Java virtual threads 223 MB, Python 240MB, Go 269MB, Elixir 445MB
  • 100만개 태스크: Rust Tokio 213MB, C# 461MB, Node.js 494MB, Rust async-std 527MB, Java virtual thread 1154MB, Python 2232MB, Go 2658MB, Elixir 4009MB

결론

  • Rust tokio는 타의 추종을 불허함
  • C# 이 풋프린트는 크지만, 매우 경쟁력 있음(Rust를 이기기도)
  • Go 는 1백만개로 가면서 자바 가상쓰레드와 격차가 벌어짐(Go가 JVM에 비해 가볍다는 일반적인 생각을 뒤엎음)
  • 메모리 사용량만 살펴본거라 다른 요소들은 고려되지 않음
  • 1백만개 태스크에서는 작업을 시작하는데 오버헤드가 많아지고, 대부분의 코드가 완료하는데 12초 이상 걸림
  • 다른 벤치마크도 실행할 예정

Go를 쓰고 Rust를 계속 기웃거리면서, 과연 이 빡빡한 문법에 적응할 필요가 있을 것인가 고민하는 경우에 꽤나 유의미한 벤치마크네요. Go가 OOM으로 죽을 상황에서도 Rust라면 잘 버틴다면.... 투자할 가치가 충분하겠네요.
물론 Rust 개발자 구하기가 훨씬 어려운게 여전히 문제긴 하겠습니다만...

Go 는 개별 고루틴마다 스택 (2KB) 이 하나씩 할당되면서 O(n) 만큼 사용량이 늘어나는 구조라 스레드 개수가 늘어날수록 불리해지는건 사실인데....

사소하게 궁금해지는것은 1만개 스레드를 넘기는 상황이 얼마나 자주있을려나요. 실제 코드 돌아가는것보다 컨텍스트스위칭이 더 자주 발생할꺼 같은....

코틀린 코루틴은 어떨지 궁금하네요

JVM으로 돌릴 거라면 큰 차이는 없을 겁니다.

Elixir의 결과가 가장 놀라운데요, Erlang이 Go 보다도 가벼운 수백 워드 수준의 메모리만 잡아먹는다고 알고 있었는데...

Erlang 공식 문서를 찾아보니, Erlang 프로세스 하나를 스폰하는 데는 338워드가 필요하다고 합니다. 그리고 64비트 시스템에서 1워드는 8바이트라고 하니, Erlang 프로세스 하나는 약 2.7KB(338 × 8 = 2,704)의 메모리를 차지하겠군요. Go 언어에서 goroutine 스택 하나 크기가 약 2.0KB라고 하니, Erlang 쪽이 메모리를 더 먹는다고 봐야 할 것 같습니다.

그렇다면 단순 계산으로 1백만 개의 Erlang 프로세스는 2.7GB의 메모리를 차지해야 하는데, 위에서 소개된 Elixir 벤치마크에서는 약 4.0GB의 최대 메모리 사용량이 관찰되었으니 1.3GB의 메모리가 더 사용된 셈입니다. 단순히 계산하면 이 시나리오에서 Erlang 프로세스 하나당 1.3KB의 메모리가 더 쓰였다는 의미인데, 잘은 모르겠지만 Erlang 프로세스 수가 일정 한도 이상으로 늘어나면 런타임에서 뭔가 추가적인 메모리 공간 사용이 필요한 건가 싶기도 합니다.

수퍼비전 트리 맵이나 메세지큐 커패시티를 잡아놓는 것 때문이 아닐까 추측해봅니다.

러스트는 페러다임에서 성능까지 정말 끝내주는 언어라고 생각되네요

비동기와 쓰레드 방식의 비교, 거기다가 언어 런타임들도 엮인 벤치마크는 관점에 따라 틀릴 수 있어서 참고해서 보세요.
HN의 댓글들도 같이 보시고요. https://news.ycombinator.com/item?id=36024209