- Mono runtime ที่ Unity ใช้งานมีความเร็วในการรันต่ำกว่า .NET รุ่นใหม่อย่างชัดเจน และมีกรณีที่โค้ด C# เดียวกันให้ผลต่างกันได้สูงสุด 15 เท่า
- ในโค้ดเกมจริง การรันบน Unity ที่อิงกับ Mono ใช้เวลา 100 วินาที ขณะที่รันโค้ดเดียวกันบน .NET ใช้เวลา 38 วินาที ซึ่งส่งผลอย่างมากต่อประสิทธิภาพของการดีบักและการทดสอบ
- แม้ใน โหมด Release Mono ก็ยังใช้เวลา 30 วินาที ส่วน .NET ใช้เวลา 12 วินาที ทำให้แม้ในสภาพแวดล้อมที่ปรับแต่งแล้วก็ยังมี ความต่างด้านประสิทธิภาพมากกว่า 2.5 เท่า
- สาเหตุคือ JIT compilation ที่ไม่มีประสิทธิภาพและการ inlining ที่ล้มเหลว ของ Mono รวมถึงการคัดลอกหน่วยความจำมากเกินไป ซึ่งต่างจาก CoreCLR JIT optimization สมัยใหม่ของ .NET
- หาก Unity ทำ การทำ .NET ให้ทันสมัยบนพื้นฐาน CoreCLR เสร็จสมบูรณ์ ก็จะสามารถยกระดับประสิทธิภาพได้อย่างมากทั้งในเกมและเอดิเตอร์ และมีแนวโน้มจะช่วย ลบภาษีประสิทธิภาพที่ซ่อนอยู่ ของทุกโปรเจกต์ Unity
เบื้องหลังการใช้ Mono ของ Unity
- Unity ใช้ เฟรมเวิร์ก Mono ในการรันโค้ด C# มาตั้งแต่ปี 2006
- ในเวลานั้น Mono คือ implementation ของ .NET แบบข้ามแพลตฟอร์ม เพียงตัวเดียว และเป็นโอเพนซอร์สที่ Unity สามารถปรับแก้ได้
- หลังปี 2014 Microsoft เปิดซอร์ส .NET Core และออก .NET Core 1.0 ในปี 2016
- หลังจากนั้นระบบนิเวศ .NET ก็พัฒนาอย่างรวดเร็ว ทั้ง Roslyn compiler, JIT ตัวใหม่ และ การปรับปรุงประสิทธิภาพ
- ในปี 2018 วิศวกรของ Unity เปิดเผยว่ากำลังทำ งานพอร์ต CoreCLR และคาดหวัง ประสิทธิภาพดีขึ้น 2~10 เท่า เมื่อเทียบกับ Mono
- อย่างไรก็ตาม จนถึงปลายปี 2025 ก็ยัง ไม่สามารถรันเกมบนพื้นฐาน CoreCLR ได้
ช่องว่างด้านประสิทธิภาพระหว่าง Mono กับ .NET
- มีการนำ โค้ดซิมูเลชัน ของโปรเจกต์ Unity ไปรันบน .NET นอก Unity เพื่อเปรียบเทียบโดยตรง
- สภาพแวดล้อม Unity/Mono: 100 วินาที, สภาพแวดล้อม .NET: 38 วินาที (อ้างอิงโหมด Debug)
- ใน โหมด Release ความต่างก็ยังคงอยู่ โดย Mono ใช้ 30 วินาที และ .NET ใช้ 12 วินาที
- .NET มี การปรับแต่งมัลติเธรด ที่ยอดเยี่ยม เช่น สร้างแผนที่ขนาด 4K×4K ได้ภายใน 3 วินาที
- สาเหตุหลักคือ การสร้างโค้ดที่ไม่มีประสิทธิภาพของ Mono ซึ่งทำให้แม้แต่ในลูปธรรมดาก็เกิด ความต่างด้านความเร็วถึง 15 เท่า
เปรียบเทียบแอสเซมบลี: Mono vs .NET
- ผลการเปรียบเทียบ x64 assembly ที่สร้างจากโค้ดทดสอบเดียวกันพบว่า
- .NET JIT จะย้าย loop invariant ออกไปไว้นอกลูป (hoisting) และทำงานด้วย การคำนวณผ่านรีจิสเตอร์ เท่าที่จำเป็น
- Mono กลับทำการคัดลอกหน่วยความจำซ้ำ ๆ ด้วย คำสั่ง
mov หลายสิบคำสั่ง และมี การ inlining ที่ไม่มีประสิทธิภาพ จนทำให้ประสิทธิภาพลดลง
- เวลาที่ใช้ในการรันลูปซ้ำ
int.MaxValue
- .NET: 750ms, Mono: 11,500ms, Unity Editor(Debug): 67,000ms
- Mono ทำการย้ายข้อมูลในหน่วยความจำและการเปรียบเทียบที่ไม่จำเป็นซ้ำไปซ้ำมาภายในลูป
ความหมายของการนำ CoreCLR มาใช้
- CoreCLR มีความสามารถสมัยใหม่ เช่น JIT รุ่นใหม่, Span<T> API, SIMD optimization, การรองรับคำสั่งฮาร์ดแวร์
- ความสามารถเหล่านี้มีโอกาสทำให้ ประสิทธิภาพเพิ่มขึ้นได้มากกว่า 2 เท่าเพิ่มเติม
- Burst compiler ของ Unity สร้าง native code บนพื้นฐาน LLVM แต่ก็มี ข้อจำกัดของฟีเจอร์ C#
- JIT สมัยใหม่ของ CoreCLR สามารถให้ประสิทธิภาพใกล้เคียงกับ Burst ได้ ขณะที่มี ข้อจำกัดด้านภาษาน้อยกว่า
- CoreCLR รองรับ AOT (การคอมไพล์ล่วงหน้า) เพื่อช่วย ปรับปรุงความเร็วในการเริ่มต้น และรองรับ แพลตฟอร์มที่จำกัด JIT (เช่น iOS)
- อย่างไรก็ตาม Unity ยังระบุว่าจะ คงนโยบายใช้ IL2CPP ต่อไป
บทสรุป: Unity จำเป็นต้องทำ .NET ให้ทันสมัย
- Mono มี ประสิทธิภาพการรันช้ากว่า .NET รุ่นใหม่ 1.5~3 เท่าหรือมากกว่า และสิ่งนี้ทำหน้าที่เป็น ต้นทุนแฝง ของทุกโปรเจกต์ Unity
- ผลที่คาดหวังเมื่อใช้ CoreCLR
- ประสิทธิภาพรันไทม์ดีขึ้น, รอบการบิลด์ซ้ำเร็วขึ้น, GC ดีขึ้น, เลิกใช้ domain reload, เพิ่มสัดส่วน managed code
- แม้โรดแมป Unity 6.x จะมี .NET Modernization รวมอยู่ แต่กำหนดยังอยู่ในช่วง หลังปี 2026
- หากการรองรับ CoreCLR เสร็จสมบูรณ์ ก็มีโอกาสมอบ การยกระดับประสิทธิภาพอย่างแท้จริง ให้ทั้งนักพัฒนา Unity และผู้เล่น
- ณ ตอนนี้ ข้อจำกัดของ Mono ยังคงเป็น คอขวดด้านประสิทธิภาพ ของระบบนิเวศ Unity โดยรวม
13 ความคิดเห็น
อา... ดูเหมือนว่า Mono จะยังคงอิงอยู่กับ legacy .NET framework สินะ...
ไม่ใช่เกมนะ แต่ตอนนี้กำลังย้ายแอปการเงิน WinForm ขนาดราว 100,000 บรรทัด ที่ใช้ .NET4.8 + LINQ to SQL ไปเป็น .NET10 + Entity Framework อยู่ และรู้สึกได้เลยว่ามันเร็วขึ้นมาก บางงานคำนวณที่เคยใช้เวลา 10 วินาทีก็ลดลงเหลือ 3 วินาทีเลย!
น่าจะดีถ้าเพิ่มความเข้ากันได้กับ NuGet ด้วย (หรือว่าเป็นเพราะผมไม่ค่อยรู้เรื่อง Unity เท่าไหร่นะ?)
แม้จะไม่ได้รองรับอย่างเป็นทางการ... แต่ก็มีโอเพนซอร์สชื่อ NuGetForUnity อยู่ครับ
ในทางทฤษฎีแล้ว แพ็กเกจ NuGet ที่ตั้งค่าให้รองรับ .NET Standard 2.0 ควรจะสามารถนำเข้ามาใช้ในสภาพแวดล้อมของ Unity ได้ด้วย...แต่ดูเหมือนว่าอย่างไรก็ยังมีจุดที่ใช้งานไม่สะดวกอยู่ไม่น้อย
https://learn.microsoft.com/ko-kr/dotnet/…
ก็พูดได้ถูกอยู่หรอก แต่ไม่ค่อยเข้าใจจริง ๆ ว่าทำไมถึงต้องเอา performance ของ editor มาเทียบกันด้วย... อย่างน้อยถ้าจะเทียบก็น่าจะเอา debug build มาด้วยไหม? หรือว่าไม่ใช่ แบบนั้นจะยิ่งทำให้น่าเชื่อน้อยลงกว่าเดิม? อีกมุมหนึ่ง IL2CPP กับ Mono ก็ดูเหมือนจะเป็นเทคโนโลยีที่ล้าสมัยพอ ๆ กันทั้งคู่อยู่ดี
สำหรับโปรเจกต์ขนาดใหญ่ ประสิทธิภาพของเอดิเตอร์ก็มีความหมายเช่นกัน เพราะมันบั่นทอนประสบการณ์การพัฒนาอย่างมาก ทั้งการเปิดเอดิเตอร์ที่ช้า การอิมพอร์ตแอสเซ็ตที่ช้า และลูปการดีบัก/ทดสอบที่ช้า...
อา... แน่นอนว่านี่ก็สำคัญเหมือนกันครับ ตอนที่ผมอ่านครั้งแรก ผู้เขียนดูเหมือนอยากพูดถึงความเร็วในการรันโค้ดในเชิงพื้นฐานมากกว่านิดหน่อยนะครับ อย่างที่คุณบอก Unity เองก็ช้า ทั้งตัว editor ก็ช้า การ import ก็ช้า และ test loop โดยรวมก็ช้า ซึ่งก็เป็นความจริงเหมือนกัน...
ดีใจมากที่ได้เห็นบทความเกี่ยวกับ Unity
อ่านได้เพลินมากครับ
ถ้านำมาใช้ได้สำเร็จ ก็คาดว่างานปรับแต่งประสิทธิภาพของเกมอินดี้ที่มีอยู่ดาษดื่นน่าจะดีขึ้น...
อีกเหตุผลหนึ่งที่ Mono จำเป็นต้องถูกทำให้ทันสมัยด้วย CoreCLR ก็คือ ผมคิดว่า Unity คงไม่ได้มีทั้งทรัพยากรหรือความตั้งใจมากพอที่จะลงทุนกับการปรับปรุงประสิทธิภาพของ Mono มรดกตกค้างจากยุค .NET Framework ก็ควรถูกสะสางให้หมดโดยเร็วที่สุดเท่าที่จะทำได้ :-D
และผมคิดว่าก็น่าจะดีถ้ามีการพิจารณาด้วยว่า ตั้งแต่ .NET 10 เป็นต้นมา ปัญหาที่พวกเขาเคยอยากแก้ด้วย IL2CPP นั้น แม้จะเป็นคนละแนวทาง แต่ตอนนี้ก็มีการจัดการอย่างตรงจุดแล้วเช่นกัน (Native AOT)
แน่นอนว่าข้อจำกัดคือมันไม่ได้สร้างโค้ด C++ ที่แก้ไขระหว่างทางได้ แต่สุดท้ายแล้วการสร้าง native binary ที่ไม่เกิด Just-In-Time ก็เริ่มต้นตั้งแต่ .NET 8 และใน .NET 10 ก็ยิ่งพัฒนาจนสุกงอมมากขึ้น
ด้วยเหตุนี้ ผมจึงมองว่าการเลื่อนการปรับให้ทันสมัยด้วย CoreCLR ออกไปเรื่อย ๆ ไม่น่าใช่ทางเลือกที่ดีสำหรับ Unity หรือไม่เช่นนั้น การเปลี่ยนไปใช้ภาษาอื่นหรือพื้นฐานอื่นแบบยกเครื่องทั้งหมดอาจจะมีประสิทธิผลมากกว่าก็ได้!
> Unity น่าจะไม่มีทั้งทรัพยากรหรือความตั้งใจมากนักที่จะลงทุนเพื่อปรับปรุงประสิทธิภาพของ Mono
ผมเห็นด้วยอย่างยิ่งกับเรื่องนี้เช่นกัน...
ความคิดเห็นจาก Hacker News
ในบทความมีอยู่หลายจุดที่ดูเหมือนเขียนโดยคนที่มีประสบการณ์พัฒนา Unity ไม่มากนัก
สรุปคือ เหตุผลที่นักพัฒนา Unity คาดหวังกับอัปเดตนี้ ไม่ได้อยู่ที่ประสิทธิภาพที่ดีขึ้น แต่เป็น การเข้าถึงฟีเจอร์ภาษาสมัยใหม่ มากกว่า และโดยทั่วไปก็มักลด GC ระหว่างรันไทม์ให้เหลือน้อยที่สุด หรือหลบไปใช้ หน่วยความจำที่ไม่ถูกจัดการ กับ DOTS แทน
IL2CPP ก็เป็นแค่ ตัวสร้างโค้ดคุณภาพต่ำ ที่แปลง .NET IL เป็น C++ แล้วไปพึ่งคอมไพเลอร์ฝั่งออปติไมซ์อีกที
ดูได้จาก โครงสร้างภายในของ IL2CPP ในบล็อก Unity
Burst/HPC# ก็เดินตามเทรนด์อย่าง ECS หรือ SoA แต่ประสิทธิภาพยังด้อยกว่า C++ ที่เขียนดีๆ หรือ C# บน CoreCLR
แถมเทคโนโลยีเหล่านี้ยัง ปิดและผูกกับ Unity เท่านั้น ใช้นอก Unity ไม่ได้ Unity ชอบทำการตลาดด้วยเบนช์มาร์กเทียบกับ Mono ที่ช้าอยู่แล้ว
สุดท้าย Unity ก็ต้องยอมรับ CoreCLR อยู่ดี และพอถึงตอนนั้นก็คงได้เห็นความจริงว่า โค้ด C# ทั่วไปเร็วกว่า โค้ดซับซ้อนที่มีอยู่เดิม
เหตุผลที่ไม่ใช้ IL2CPP เพราะมันเข้ากันไม่ได้กับ การโหลด DLL ระหว่างรันไทม์, reflection, การจัดแพ็ก struct ด้วย FieldOffset เป็นต้น
ม็อดเดอร์ยังสามารถขยายความสามารถด้วย IL injection ได้ ซึ่งทำให้พัฒนาได้เร็วขึ้นโดยรวม
ฉันไม่ชอบ Burst กับ HPC# เพราะมีทั้งความซับซ้อนและข้อจำกัดมากเกินไป ความต่างด้านประสิทธิภาพระหว่าง Mono กับ .NET ทำให้ยิ่งน่าหงุดหงิด
การโปรไฟล์ในเอดิเตอร์ก็ยังมีประโยชน์ เพราะแสดงสัดส่วนการปรับปรุงประสิทธิภาพใกล้เคียงกับบิลด์จริง เพียงแต่โปรไฟเลอร์พื้นฐานของ Unity ไม่แม่นยำ เลยใช้ ระบบติดตามที่ทำเอง
GC ยังเป็นปัญหาอยู่ การจัดการสตริงหรือ UI สร้าง ขยะ ทุกเฟรม ถ้าไป CoreCLR ก็จะได้ API ที่ดีกว่าและ moving GC ที่ช่วยลดปัญหา memory fragmentation ได้
Asset Store ยอดเยี่ยมก็จริง แต่ตัวเอนจินเองยังให้ความรู้สึกไม่ค่อยขัดเกลา
ระบบสคริปต์ที่อิง Mono ก็ซับซ้อนเชิงสถาปัตยกรรมเกินกว่าจะย้ายไป CoreCLR ได้ง่าย
ถ้า Unity อยากปรับปรุง Core อย่างจริงจัง ก็ต้อง ออกแบบเอดิเตอร์ใหม่ทั้งตัวแบบ Blender 3.x
ตอนนี้ยังให้ความรู้สึกเหมือน UI จากปี 1999
ปลั๊กอินกับเครื่องมือจำนวนมากค้างอยู่ที่ขั้น “0.x-preview” แล้ว 5~10 ปีต่อมาก็ใช้ไม่ได้ หรือถูกทรัพย์สินใหม่ๆ กลบไป
เพราะแบบนี้ตอนนี้ฉันจึงใช้เฉพาะ เวอร์ชัน 1.0 ขึ้นไป เท่านั้น ไม่อย่างนั้นก็จะไปผูกกับปลั๊กอินที่ถูกทิ้ง สุดท้ายต้องพอร์ตใหม่อยู่ดี
Unity, นักพัฒนา และผู้ใช้ ต่างก็เสียประโยชน์กันหมด
ภายในเองก็ ล้มเหลวในการพัฒนาเกมของตัวเอง จนขาดความรู้สึกต่อการทำเกมจริง
แค่เพิ่มฟีเจอร์ตามที่มีคนขอ แต่ไม่มี วิสัยทัศน์ที่สอดคล้องกัน
ถ้าประสิทธิภาพสำคัญก็ควรเรียก Vulkan โดยตรง แต่ถ้า ความพกพา สำคัญก็ควรใช้ WebGPU
แต่ละเบราว์เซอร์มี implementation ต่างกันจึงเกิด overhead ได้ แต่ถ้ามี WebGPU ในระดับไดรเวอร์ของ OS ก็แก้ได้
ในขณะที่ Godot ให้ building blocks ที่เรียบง่ายและมีความหมาย ทำให้สร้างสิ่งที่ต้องการได้อย่างอิสระ
Asset Store ก็เช่นกัน ปัญหาความเข้ากันได้ของเวอร์ชันทำให้ดูแลรักษายาก และสุดท้ายส่วนใหญ่ก็กลายเป็น ทรัพย์สินที่ถูกทิ้งร้าง
ถ้า Unity ซื้อทรัพย์สินที่มีประโยชน์มา ก็ไม่ค่อยรวมเข้ากับระบบให้ดี และทรัพย์สินคู่แข่งก็หายไป
ตรงกันข้าม Unreal Engine ให้ฟีเจอร์พวกนี้มาในระดับ ความสามารถที่ฝังในเอนจิน
และ Unity ก็ยังไม่มีแผนใส่ GC ที่ดีกว่าเข้าไปใน IL2CPP
ถ้ามีเอดิเตอร์ที่อิง CoreCLR ออกมา เอดิเตอร์อาจเร็วกว่าบิลด์จริงเสียอีก
การอภิปรายที่เกี่ยวข้อง: Unity CoreCLR และการปรับปรุง .NET ให้ทันสมัย
ถ้า incremental GC ทำงานได้ดี ปัญหา stutter ก็ไม่ร้ายแรงนัก
ตอนนี้ C# เร็วขึ้นโดยพื้นฐานมากแล้ว ดังนั้น Unity ต้อง ทุ่มกำลังทั้งหมด ให้กับการเปลี่ยนผ่านครั้งนี้
ทีมของเราใช้เวลาหลายเดือนในการย้ายจาก .NET Framework 4.7.2 ไป .NET 6 และหลังจากนั้นการอัปเกรด LTS รุ่นถัดๆ ไปก็ง่ายระดับ ไม่กี่ชั่วโมง
มีแต่ความล่าช้าต่อเนื่อง และผู้นำก็ทยอยลาออก
ทางเลือกที่แนะนำคือ Stride engine ที่อิง .NET 10 ซึ่งไม่มี boundary overhead แบบ Unity
Godot เป็นโอเพนซอร์สก็จริง แต่การรองรับ C# ยังไม่เสถียร และถ้าทำ Web build ไม่ได้ ก็ ไม่เหมาะกับ game jam
จึงต้องการ โซลูชัน sandbox ที่แท้จริง ซึ่งมีการรองรับ GPU ด้วย
ลำดับความสำคัญเปลี่ยนตลอด และพอข้อกำหนดเปลี่ยน งานก็ต้องทำซ้ำ
นักพัฒนายังเก่งกันมาก แต่ขาด แรงผลักดันที่สม่ำเสมอ
การรีไรต์ขนาดใหญ่อย่างนี้ สำหรับ CEO เองก็เป็น การตัดสินใจที่เสี่ยงสูง
ผลคือประสิทธิภาพดีขึ้นมาก และเพราะ ลดการผูกกับเอนจิน จึงดูแลรักษาโค้ดได้ง่ายขึ้น
การเปิดเผยแนวคิดของ Unity เฉพาะจุดที่จำเป็น และใช้การทดสอบมาบังคับขอบเขต ทำให้ฉันเห็นคุณค่าของ การแยกเชิงตรรกะ อย่างชัดเจน
ถ้ามี root access และจับคู่กับเครื่องมือเครือข่ายอย่าง WireGuard หรือ Tailscale มันก็เหมาะเป็น เซิร์ฟเวอร์พกพา มาก
ด้วย GC ใหม่ของ .NET 10 อาการ stutter ในเกมก็น่าจะหายไปเกือบหมด
ตอนนี้ฉันสตรีมเกมจากพีซีหลักมาเล่นบนมือถือด้วย Sunlight + Moonlight
ด้วยจอ OLED รีเฟรชเรตสูง ทำให้กินแบตไม่มากด้วย
แม้จะไม่ใช่ .NET SDK แต่ก็ ฝัง Mono runtime มาในแอป ทำให้ประสบการณ์โดยรวมคล้ายกัน
ทั้งที่ ข้อได้เปรียบด้านข้ามแพลตฟอร์มของ Mono หายไปแล้ว แต่ก็ยังไม่เข้าใจว่าทำไมถึงยังคงแฮ็กซับซ้อนอย่าง IL2CPP ไว้
มีการแก้แบบ นอกมาตรฐาน สะสมมาหลายปี จนยากจะออปติไมซ์ใหม่ เว้นแต่จะเป็นบริษัทใหญ่จริงๆ
สำหรับโปรเจกต์ระดับนี้ ฉันคิดว่า 1~2 ปีก็น่าจะพอ