# 1백만개의 동시 작업을 실행하는데 메모리가 얼마나 필요할까?

> Clean Markdown view of GeekNews topic #9250. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=9250](https://news.hada.io/topic?id=9250)
- GeekNews Markdown: [https://news.hada.io/topic/9250.md](https://news.hada.io/topic/9250.md)
- Type: news
- Author: [xguru](https://news.hada.io/@xguru)
- Published: 2023-05-24T11:06:02+09:00
- Updated: 2023-05-24T11:06:02+09:00
- Original source: [pkolaczk.github.io](https://pkolaczk.github.io/memory-consumption-of-async/)
- Points: 30
- Comments: 9

## Topic Body

- 비동기 와 멀티쓰레드 간의 메모리 사용량을 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초 이상 걸림   
- 다른 벤치마크도 실행할 예정

## Comments



### Comment 16261

- Author: bus710
- Created: 2023-05-25T11:18:39+09:00
- Points: 1

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

### Comment 16244

- Author: kuber
- Created: 2023-05-24T17:04:01+09:00
- Points: 3

Go 는 개별 고루틴마다 스택 (2KB) 이 하나씩 할당되면서 O(n) 만큼 사용량이 늘어나는 구조라 스레드 개수가 늘어날수록 불리해지는건 사실인데....  
  
사소하게 궁금해지는것은 1만개 스레드를 넘기는 상황이 얼마나 자주있을려나요. 실제 코드 돌아가는것보다 컨텍스트스위칭이 더 자주 발생할꺼 같은....

### Comment 16238

- Author: kotliner
- Created: 2023-05-24T14:25:59+09:00
- Points: 1

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

### Comment 16243

- Author: [hidden]
- Created: 2023-05-24T16:23:37+09:00
- Points: 1
- Parent comment: 16238
- Depth: 1

[숨김 처리된 댓글입니다]

### Comment 16235

- Author: secret3056
- Created: 2023-05-24T11:45:27+09:00
- Points: 1

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

### Comment 16248

- Author: kunggom
- Created: 2023-05-24T22:21:02+09:00
- Points: 1
- Parent comment: 16235
- Depth: 1

[Erlang 공식 문서](https://www.erlang.org/doc/efficiency_guide/advanced.html)를 찾아보니, Erlang 프로세스 하나를 스폰하는 데는 338워드가 필요하다고 합니다. 그리고 64비트 시스템에서 1워드는 8바이트라고 하니, Erlang 프로세스 하나는 약 2.7KB(338 × 8 = 2,704)의 메모리를 차지하겠군요. Go 언어에서 [goroutine 스택 하나 크기](https://go.dev/doc/go1.4#runtime)가 약 2.0KB라고 하니, Erlang 쪽이 메모리를 더 먹는다고 봐야 할 것 같습니다.  
  
그렇다면 단순 계산으로 1백만 개의 Erlang 프로세스는 2.7GB의 메모리를 차지해야 하는데, 위에서 소개된 Elixir 벤치마크에서는 약 4.0GB의 최대 메모리 사용량이 관찰되었으니 1.3GB의 메모리가 더 사용된 셈입니다. 단순히 계산하면 이 시나리오에서 Erlang 프로세스 하나당 1.3KB의 메모리가 더 쓰였다는 의미인데, 잘은 모르겠지만 Erlang 프로세스 수가 일정 한도 이상으로 늘어나면 런타임에서 뭔가 추가적인 메모리 공간 사용이 필요한 건가 싶기도 합니다.

### Comment 16263

- Author: bus710
- Created: 2023-05-25T12:35:54+09:00
- Points: 1
- Parent comment: 16248
- Depth: 2

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

### Comment 16233

- Author: humblebee
- Created: 2023-05-24T11:31:57+09:00
- Points: 2

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

### Comment 16232

- Author: xguru
- Created: 2023-05-24T11:07:01+09:00
- Points: 3

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