2 คะแนน โดย GN⁺ 2024-11-30 | 1 ความคิดเห็น | แชร์ทาง WhatsApp

เบนช์มาร์ก

  • คอร์รูทีนคืออะไร?

    • คอร์รูทีนเป็นองค์ประกอบของโปรแกรมคอมพิวเตอร์ที่สามารถหยุดและกลับมาทำงานต่อได้ โดยเป็นการขยายแนวคิดของซับรูทีนสำหรับการทำงานหลายอย่างพร้อมกันแบบร่วมมือกัน
    • เหมาะสำหรับการใช้งานในองค์ประกอบของโปรแกรม เช่น งานแบบร่วมมือกัน ข้อยกเว้น event loop iterator ลิสต์อนันต์ และ pipe
  • Rust

    • เขียนโปรแกรม 2 แบบ: โปรแกรมที่ใช้ tokio และ async_std
    • ทั้งสองเป็น asynchronous runtime ที่นิยมใช้กันทั่วไปใน Rust
  • C#

    • C# รองรับ async/await เช่นเดียวกับ Rust
    • ตั้งแต่ .NET 7 เป็นต้นมา มีการคอมไพล์แบบ NativeAOT ทำให้สามารถรัน managed code ได้โดยไม่ต้องมี VM
  • NodeJS

    • ใช้ Promise.all สำหรับงานแบบอะซิงโครนัส
  • Python

    • ใช้โมดูล asyncio เพื่อทำงานแบบอะซิงโครนัส
  • Go

    • ใช้ goroutine ในการทำ concurrent และใช้ WaitGroup เพื่อรอให้งานเสร็จ
  • Java

    • ตั้งแต่ JDK 21 เป็นต้นมา มี virtual thread ซึ่งเป็นแนวคิดที่คล้ายกับ goroutine
    • สามารถใช้ GraalVM เพื่อสร้าง native image ได้

สภาพแวดล้อมการทดสอบ

  • ฮาร์ดแวร์: 13th Gen Intel(R) Core(TM) i7-13700K
  • ระบบปฏิบัติการ: Debian GNU/Linux 12 (bookworm)
  • Rust: 1.82.0
  • .NET: 9.0.100
  • Go: 1.23.3
  • Java: openjdk 23.0.1
  • Java (GraalVM): java 23.0.1
  • NodeJS: v23.2.0
  • Python: 3.13.0

ผลลัพธ์

  • การใช้หน่วยความจำต่ำสุด

    • Rust, C# (NativeAOT) และ Go ถูกคอมไพล์เป็น native binary จึงใช้หน่วยความจำน้อย
    • Java (GraalVM native image) ก็แสดงผลได้ดีเช่นกัน แต่ยังใช้หน่วยความจำมากกว่าภาษาแบบคอมไพล์ล่วงหน้าอื่น ๆ
  • 10K งาน

    • Rust แทบไม่มีการเพิ่มขึ้นของการใช้หน่วยความจำ
    • C# (NativeAOT) ก็ใช้หน่วยความจำน้อยเช่นกัน
    • Go ใช้หน่วยความจำมากกว่าที่คาดไว้
  • 100K งาน

    • Rust และ C# แสดงประสิทธิภาพที่ดี
    • C# (NativeAOT) ใช้หน่วยความจำน้อยกว่า Rust
  • 1 ล้านงาน

    • C# ทิ้งห่างทุกภาษาและใช้หน่วยความจำน้อยที่สุด
    • Rust ก็มีประสิทธิภาพด้านหน่วยความจำที่ยอดเยี่ยม
    • Go ใช้หน่วยความจำมากเมื่อเทียบกับภาษาอื่น

บทสรุป

  • งานพร้อมกันจำนวนมากอาจใช้หน่วยความจำอย่างมาก แม้จะไม่ได้ทำงานที่ซับซ้อนก็ตาม
  • การพัฒนาของ .NET และ NativeAOT นั้นโดดเด่น และ Java native image ที่สร้างด้วย GraalVM ก็มีประสิทธิภาพด้านหน่วยความจำยอดเยี่ยมเช่นกัน
  • goroutine ยังคงไม่มีประสิทธิภาพในแง่การใช้ทรัพยากร

ภาคผนวก

  • ใน Rust (tokio) การใช้ลูป for แทน join_all ช่วยลดการใช้หน่วยความจำลงได้ครึ่งหนึ่ง ทำให้ Rust กลายเป็นผู้นำอย่างชัดเจนในเบนช์มาร์กครั้งนี้

1 ความคิดเห็น

 
GN⁺ 2024-11-30
ความคิดเห็นจาก Hacker News
  • เบนช์มาร์กไม่ได้สะท้อนความแตกต่างของวิธีประมวลผลแบบอะซิงโครนัสของ Node และ Go ได้อย่างเหมาะสม โดย Node ใช้ Promise.all ส่วน Go ใช้ goroutine จึงมีความแตกต่างกัน น่าสนใจหากจะเปรียบเทียบการใช้หน่วยความจำระหว่างงาน async I/O กับงานที่เป็น CPU-bound

  • อธิบายความแตกต่างระหว่าง “งานที่รอ 10 วินาที” กับ “งานที่จะถูกปลุกหลังจาก 10 วินาที” โดยการใช้หน่วยความจำของโค้ด Go แตกต่างจากโค้ดอื่นค่อนข้างมาก

  • เสนอวิธีเปรียบเทียบ Go กับ Node อย่างยุติธรรม โดยใช้ goroutine สำหรับจัดตารางเวลา timer และ goroutine สำหรับจัดการสัญญาณจาก timer พร้อมตั้งข้อสังเกตว่าที่ไม่มี Bun และ Deno รวมอยู่ใน Node นั้นดูแปลก

  • งานพร้อมกันจำนวนมากอาจใช้หน่วยความจำมาก แต่ถ้าข้อมูลต่องานมีขนาดมากกว่าหลาย KB โอเวอร์เฮดหน่วยความจำของตัวจัดตารางเวลาก็เล็กน้อยจนแทบมองข้ามได้

  • การใช้หน่วยความจำอาจแตกต่างกันได้ตามนิยามของ “งานพร้อมกัน” โดยในการติดตั้งใช้งานที่มีประสิทธิภาพ งานพร้อมกัน 1M ต้องใช้ประมาณ 200MB

  • ชี้ว่า Go ตามหลัง Java ในด้านการใช้หน่วยความจำมากกว่า 2 เท่า และกล่าวว่าเบนช์มาร์กนี้ไม่ได้เป็นตัวแทนของโปรแกรมจริง

  • การเปรียบเทียบภาษาด้วยโค้ดง่าย ๆ อาจไม่ยุติธรรมกับนักพัฒนา และแนะนำให้เพิ่มงานจริงเข้าไปเพื่อวัดทั้งการใช้หน่วยความจำและความแตกต่างด้านการจัดตารางเวลา

  • กล่าวว่าบ่อยครั้งเบนช์มาร์กล้วนเต็มไปด้วยข้อผิดพลาด และไม่เข้าใจแรงจูงใจของคนที่เผยแพร่เบนช์มาร์กแบบนี้

  • มีความเป็นไปได้ว่า Java เบนช์มาร์กทำผิด เพราะไม่ได้กำหนดขนาดเริ่มต้นของ ArrayList ทำให้มีการสร้างอ็อบเจ็กต์ที่ไม่จำเป็นจำนวนมาก

  • อธิบายว่าทำไมโค้ดอะซิงก์ของ Rust จึงเสร็จเร็วกว่าที่คาด โดย tokio::time::sleep() ติดตามเวลาที่ future ถูกสร้างขึ้น