ต้องใช้หน่วยความจำเท่าไรในปี 2024 เพื่อรันงานพร้อมกัน 1 ล้านงาน?
(hez2010.github.io)มีการรันโปรแกรมที่ประมวลผลงานพร้อมกัน 1 ล้านงานด้วย Rust, C#, Python, Go, Java, NodeJS เวอร์ชันล่าสุด เพื่อเปรียบเทียบประสิทธิภาพด้านการใช้หน่วยความจำ
C# (NativeAOT) และ Rust แสดงประสิทธิภาพด้านหน่วยความจำได้ดีที่สุด ขณะที่ Go ได้รับการประเมินต่ำเนื่องจากใช้หน่วยความจำมากกว่าที่คาดไว้ โดยรวมแล้ว .NET และ Rust โดดเด่นเป็นพิเศษ และ Java (GraalVM) ก็แสดงให้เห็นถึงการปรับปรุงที่น่าประหลาดใจ
ในรายละเอียด Rust ใช้หน่วยความจำน้อยที่สุดที่ประมาณ 29MB ตามมาด้วย C# NativeAOT ที่ประมาณ 71MB ส่วน NodeJS อยู่ที่ 232MB และ Python อยู่ที่ 339MB ขณะที่ Go ใช้หน่วยความจำค่อนข้างสูงที่ 753MB ซึ่งเป็นผลลัพธ์ที่น่าเสียดาย ด้าน Java (GraalVM) แสดงการปรับปรุงครั้งใหญ่ด้วยการใช้หน่วยความจำเพียง 92MB
10 ความคิดเห็น
เมื่อดูโค้ดเบนช์มาร์กแล้ว ในกรณีของ Rust และ Python ดูเหมือนว่าในความเป็นจริงไม่ได้สร้าง concurrent task ขึ้นมา แต่เป็นเพียงการสร้างอ็อบเจ็กต์
futureที่แม้จะทำงานแบบ asynchronous แต่ก็ไม่สามารถรันแบบขนานกับ task อื่นได้จริง ผมคิดว่า C# ก็น่าจะเป็นกรณีคล้ายกัน ในทางกลับกัน โค้ด Go ถูกออกแบบมาให้สร้างgoroutineซึ่งเป็น task ที่มีcall stackเป็นต้น เป็นของตัวเอง ดังนั้นสาเหตุที่การใช้หน่วยความจำของ Go ดูพุ่งผิดปกติโดยเฉพาะในเคส 1 ล้านรายการ ก็น่าจะมาจากจุดนี้ถ้าจะพูดปกป้อง Go ล่ะก็ Go สามารถทำงานได้แม้จะมีไลบรารีอะไรก็ตามอยู่ภายในฟังก์ชันที่รันพร้อมกัน 1 ล้านตัว แค่ใส่
goก็พอแล้ว ในภาษาอื่นที่อิง async ถ้ามีไลบรารีแบบ synchronous แทรกอยู่ตรงกลางและกินเวลาแม้เพียงเล็กน้อย มันก็อาจกลายเป็นสถานการณ์ชวนปวดหัวที่ข้อดีของ async ถูกกลบหายไปหมด เพื่อใช้ประโยชน์จาก async ได้อย่างเต็ม 100% ฟังก์ชันทุกตัวที่กินเวลาแม้แต่นิดเดียวก็ต้องเปลี่ยนให้เป็น async ทั้งหมด ส่วน Java VirtualThread นี่ก็... รอบนี้ที่บริษัทของเราเคยเชื่อมั่นแล้วเดินหน้าด้วย Java VirtualThread แต่สุดท้ายเพราะปัญหาความเข้ากันได้กับไลบรารี ก็ต้องเปลี่ยนกลับไปใช้ thread ปกติ แล้วจบลงด้วยการต้องเปิดอินสแตนซ์หลายสิบตัวขอฟังเพิ่มเติมเกี่ยวกับส่วนของความเข้ากันได้อีกหน่อยได้ไหมครับ :eyes:
คงพูดได้ว่าไม่มีคำกล่าวที่มักได้ยินใน Spring ว่า "ถ้าจะใช้ WebFlux ให้ได้อย่างถูกต้อง ต้องใช้กับ R2DBC แทน JPA ถึงจะเห็นคุณค่าที่แท้จริง"
ได้ยินมาว่าไลบรารี msal ของ Microsoft ไม่ทำงานบน virtual thread
ผมคิดว่าในกรณีของไลบรารี
msalที่ยกมาเป็นตัวอย่าง หากในฝั่ง Go ก็เป็นไลบรารีที่ใช้ชนิดข้อมูลหรือโครงสร้างที่ไม่ thread-safe เช่นกัน ก็น่าจะเป็นเคสแบบเดียวกันมันเกี่ยวข้องกับ thread safety ตรงไหนหรือครับ? แต่เดิม goroutine ก็ไม่ใช่ระบบที่รับประกัน thread safety อยู่แล้วไม่ใช่หรือครับ
ขอบคุณสำหรับข้อมูลครับ
มีคำถามที่อยากถามครับ
ถ้าอย่างนั้น ภาษาอื่น ๆ นอกจาก Go ถ้ามีไลบรารีแบบ synchronous อย่างที่คุณบอกไว้ ก็จะพังหมดเลยใช่ไหมครับ?
หรือว่าจริง ๆ แล้วในบรรดาภาษาอื่น ๆ ก็มีภาษาที่รองรับ asynchronous ได้อย่างสมบูรณ์แบบเหมือน Go ด้วยหรือเปล่า อยากทราบครับ
มีคำว่า colorless อยู่ ภาษาที่ไม่จำเป็นต้องแยกระหว่างอะซิงโครนัสกับซิงโครนัสนั้นมีแค่ Go เท่านั้น จากมุมมองของผู้ใช้ เมื่อต้องเขียนโปรแกรมที่ต้องการ concurrency แล้ว Go มีจุดแข็งที่เหนือกว่ามากในด้านความยากและการใช้งาน
แม้ประสิทธิภาพอาจด้อยกว่าการเขียนโปรแกรมอะซิงโครนัสที่ปรับแต่งมาอย่างดีอยู่เล็กน้อยก็ตาม
ขออภัยที่ต้องทักนะครับ แต่ผมจะขอพูดเฉพาะส่วนที่ไม่ถูกต้องของคำว่า “พัง” และคำว่า “อะซิงก์ที่สมบูรณ์แบบ” เท่านั้นครับ แม้จะเป็นแบบอะซิงก์ก็ใช้ไลบรารีแบบซิงก์ได้ ไม่มีปัญหา ตราบใดที่รับประกันได้ว่าจะเสร็จภายในเวลาสั้น ๆ ถ้ามีการเรียกแบบซิงก์ที่ใช้เวลารันนาน ก็จะทำให้การประมวลผลอะซิงก์อื่น ๆ ช้าลง จึงเกิดปัญหาครับ ส่วน Go สามารถจัดสรรงานระดับหลายร้อยล้านงานด้วย goroutine ได้ ดังนั้นตัวภาษาเองจึงไม่มีแนวคิดเรื่องอะซิงก์ในตัว จากมุมของคนใช้งาน แค่เรียกฟังก์ชันใด ๆ โดยเติม
goเข้าไป ก็จะรันแบบขนานได้เลย สะดวกมากครับ ส่วนตัวผมคิดว่า “อะซิงก์ที่สมบูรณ์แบบ” น่าจะเป็น JavaScript เพราะรากฐานของภาษานั้นเป็นอะซิงก์ครับ