30 điểm bởi xguru 2023-05-24 | 9 bình luận | Chia sẻ qua WhatsApp
  • So sánh mức sử dụng bộ nhớ giữa bất đồng bộ và đa luồng trên các ngôn ngữ Rust, Go, Java, C#, Python, Node.js, Elixir
  • Viết chương trình bằng từng ngôn ngữ để chạy N tác vụ chờ trong 10 giây (có sự hỗ trợ của ChatGPT)
  • So sánh trên Xeon E3 + Ubuntu 22.04

Kết quả

  • Dấu chân bộ nhớ tối thiểu (thử nghiệm chỉ với 1 tác vụ): Go và Rust chỉ cần dưới 3MB, Python 17MB, Java/Node.js khoảng 40MB, C# 131MB
  • 10 nghìn tác vụ: 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
  • 100 nghìn tác vụ (không tính luồng): Rust tokio 23MB, Rust Async-std 54MB, Node.js 112MB, C# 130MB, Java virtual threads 223 MB, Python 240MB, Go 269MB, Elixir 445MB
  • 1 triệu tác vụ: Rust Tokio 213MB, C# 461MB, Node.js 494MB, Rust async-std 527MB, Java virtual thread 1154MB, Python 2232MB, Go 2658MB, Elixir 4009MB

Kết luận

  • Rust tokio vượt trội hoàn toàn
  • C# có dấu chân bộ nhớ lớn, nhưng vẫn rất cạnh tranh (thậm chí có lúc vượt Rust)
  • Khi lên tới 1 triệu tác vụ, Go bị nới rộng khoảng cách so với Java virtual threads (đảo ngược suy nghĩ phổ biến rằng Go nhẹ hơn JVM)
  • Vì chỉ xem xét mức sử dụng bộ nhớ nên các yếu tố khác không được tính đến
  • Với 1 triệu tác vụ, chi phí khởi động tăng cao và phần lớn mã mất hơn 12 giây để hoàn tất
  • Dự kiến sẽ chạy thêm các benchmark khác

9 bình luận

 
bus710 2023-05-25

Đây là một benchmark khá đáng tham khảo nếu bạn đang dùng Go nhưng vẫn cứ ngó nghiêng Rust và tự hỏi liệu có cần phải thích nghi với cú pháp ngặt nghèo đó hay không. Nếu Rust vẫn trụ tốt ngay cả trong tình huống Go chết vì OOM.... thì rõ ràng là rất đáng để đầu tư. Tất nhiên, vấn đề vẫn là tìm được lập trình viên Rust khó hơn nhiều...

 
kuber 2023-05-24

Đúng là Go có cấu trúc bất lợi dần khi số lượng thread tăng lên, vì mỗi goroutine riêng lẻ đều được cấp một stack (2KB), nên mức sử dụng tăng theo O(n)....

Điều khiến tôi hơi tò mò một chút là tình huống vượt quá 10.000 thread thực sự xảy ra thường xuyên đến mức nào nhỉ. Có vẻ như việc chuyển ngữ cảnh còn xảy ra thường xuyên hơn cả lúc đoạn mã thực tế chạy....

 
kotliner 2023-05-24

Tôi khá tò mò không biết Kotlin Coroutines sẽ thế nào.

 
[Bình luận này đã bị ẩn.]
 
secret3056 2023-05-24

Kết quả của Elixir mới là điều gây ngạc nhiên nhất; tôi biết rằng Erlang thậm chí còn nhẹ hơn cả Go, chỉ tiêu tốn bộ nhớ ở mức vài trăm word thôi mà...

 
kunggom 2023-05-24

Tôi tìm trong tài liệu chính thức của Erlang thì thấy để spawn một process Erlang cần 338 word. Và vì trên hệ thống 64-bit, 1 word là 8 byte, nên một process Erlang sẽ chiếm khoảng 2.7KB bộ nhớ (338 × 8 = 2,704). Trong khi đó, kích thước một goroutine stack trong Go vào khoảng 2.0KB, nên có vẻ phía Erlang dùng nhiều bộ nhớ hơn.

Vậy nếu tính đơn giản, 1 triệu process Erlang sẽ phải chiếm 2.7GB bộ nhớ, nhưng trong benchmark Elixir được giới thiệu ở trên lại quan sát thấy mức sử dụng bộ nhớ tối đa khoảng 4.0GB, tức là đã dùng thêm 1.3GB bộ nhớ. Nếu tiếp tục tính đơn giản thì điều đó có nghĩa là trong kịch bản này, mỗi process Erlang dùng thêm 1.3KB bộ nhớ; tôi không chắc lắm, nhưng có vẻ khi số lượng process Erlang tăng vượt một ngưỡng nhất định thì runtime có thể cần dùng thêm một phần không gian bộ nhớ bổ sung nào đó.

 
bus710 2023-05-25

Tôi đoán có lẽ là do phải dành sẵn dung lượng cho supervision tree map hoặc capacity của message queue.

 
humblebee 2023-05-24

Tôi nghĩ Rust thực sự là một ngôn ngữ tuyệt vời, từ mô hình lập trình cho đến hiệu năng.

 
xguru 2023-05-24

So sánh giữa cách tiếp cận bất đồng bộ và luồng, cộng thêm benchmark có lồng cả runtime ngôn ngữ nên có thể khác nhau tùy góc nhìn, vì vậy hãy xem với tinh thần tham khảo.
Hãy đọc thêm cả các bình luận trên HN. https://news.ycombinator.com/item?id=36024209