- Runtime Mono mà Unity sử dụng cho thấy tốc độ thực thi chậm hơn đáng kể so với .NET hiện đại, với các trường hợp cùng một mã C# có thể chênh lệch tối đa 15 lần
- Trong mã game thực tế, Unity chạy trên Mono mất 100 giây, trong khi cùng đoạn mã trên .NET được đo là 38 giây, ảnh hưởng lớn đến hiệu quả gỡ lỗi và kiểm thử
- Ngay cả ở chế độ Release, Mono mất 30 giây còn .NET là 12 giây, nên ngay trong môi trường đã tối ưu, chênh lệch hiệu năng hơn 2,5 lần vẫn được duy trì
- Nguyên nhân là do JIT compiler kém hiệu quả và thất bại trong việc inlining của Mono, sao chép bộ nhớ quá mức, v.v., đối lập với tối ưu hóa JIT CoreCLR hiện đại của .NET
- Khi Unity hoàn tất hiện đại hóa .NET dựa trên CoreCLR, hiệu năng có thể tăng mạnh ở cả game lẫn editor, và điều này được kỳ vọng sẽ xóa bỏ khoản thuế hiệu năng ẩn của mọi dự án Unity
Bối cảnh Unity sử dụng Mono
- Unity đã dùng framework Mono để chạy mã C# từ năm 2006
- Khi đó Mono là triển khai .NET đa nền tảng duy nhất, đồng thời là mã nguồn mở để Unity có thể tùy chỉnh
- Từ sau năm 2014, Microsoft công bố .NET Core dưới dạng mã nguồn mở, và phát hành .NET Core 1.0 vào năm 2016
- Từ đó, hệ sinh thái .NET phát triển nhanh chóng với trình biên dịch Roslyn, JIT mới, cải thiện hiệu năng và nhiều yếu tố khác
- Năm 2018, các kỹ sư Unity cho biết đang tiến hành port CoreCLR, và kỳ vọng hiệu năng tăng 2 đến 10 lần so với Mono
- Tuy nhiên, đến cuối năm 2025, việc chạy game dựa trên CoreCLR vẫn chưa khả thi
Khoảng cách hiệu năng giữa Mono và .NET
- Mã mô phỏng trong một dự án Unity đã được chạy bằng .NET bên ngoài Unity để so sánh trực tiếp
- Trong môi trường Unity/Mono: 100 giây, trong môi trường .NET: 38 giây (theo chế độ Debug)
- Ở chế độ Release, chênh lệch vẫn giữ nguyên với Mono 30 giây và .NET 12 giây
- .NET cho thấy tối ưu đa luồng rất tốt, chẳng hạn tạo bản đồ 4K×4K trong chưa đầy 3 giây
- Nguyên nhân chính là việc sinh mã kém hiệu quả của Mono, đến mức ngay cả trong một vòng lặp đơn giản cũng xuất hiện chênh lệch tốc độ 15 lần
So sánh assembly: Mono vs .NET
- Kết quả so sánh assembly x64 được tạo từ cùng một đoạn mã kiểm thử
- JIT của .NET đưa bất biến vòng lặp ra ngoài vòng lặp (hoisting) và chỉ thực hiện lượng phép toán trên thanh ghi tối thiểu
- Mono lặp lại việc sao chép bộ nhớ bằng hàng chục lệnh
mov, và hiệu năng giảm do inlining kém hiệu quả
- Thời gian chạy vòng lặp lặp lại
int.MaxValue
- .NET: 750ms, Mono: 11.500ms, Unity Editor(Debug): 67.000ms
- Mono lặp đi lặp lại các thao tác di chuyển bộ nhớ và phép so sánh không cần thiết bên trong vòng lặp
Ý nghĩa của việc đưa CoreCLR vào
- CoreCLR cung cấp các tính năng hiện đại như JIT mới nhất, API
Span<T>, tối ưu SIMD, hỗ trợ tập lệnh phần cứng
- Các tính năng này có khả năng mang lại mức tăng hiệu năng bổ sung hơn 2 lần
- Trình biên dịch Burst của Unity tạo mã native dựa trên LLVM, nhưng có giới hạn về tính năng C#
- JIT hiện đại của CoreCLR có thể đem lại hiệu năng tương tự Burst trong khi ít ràng buộc ngôn ngữ hơn
- CoreCLR hỗ trợ AOT (biên dịch sẵn), cho phép cải thiện tốc độ khởi động và đáp ứng các nền tảng hạn chế JIT (như iOS)
- Tuy vậy, Unity vẫn cho biết sẽ tiếp tục duy trì IL2CPP
Kết luận: Sự cần thiết của hiện đại hóa .NET trong Unity
- So với .NET hiện đại, Mono cho thấy hiệu năng thực thi chậm hơn từ 1,5 đến trên 3 lần, và đây hoạt động như một chi phí ẩn trong mọi dự án Unity
- Các hiệu quả được kỳ vọng khi đưa CoreCLR vào
- Cải thiện hiệu năng runtime, build lặp nhanh hơn, GC tốt hơn, loại bỏ domain reload, tăng tỷ trọng managed code
- Lộ trình Unity 6.x có bao gồm .NET Modernization, nhưng dự kiến là sau năm 2026
- Khi hỗ trợ CoreCLR được hoàn thiện, nó có thể mang lại bước nhảy hiệu năng thực chất cho cả nhà phát triển Unity lẫn người chơi
- Hiện tại, các giới hạn của Mono vẫn là nút thắt hiệu năng của toàn bộ hệ sinh thái Unity
13 bình luận
À... có vẻ Mono vẫn còn dựa trên legacy .NET framework...
Không phải game, nhưng tôi đang chuyển một ứng dụng tài chính WinForm khoảng 100 nghìn dòng dùng .NET 4.8 + LINQ to SQL sang .NET 10 + Entity Framework, và có thể cảm nhận rõ là nó nhanh hơn rất nhiều. Có những tác vụ tính toán từng mất 10 giây nay giảm còn 3 giây!
Sẽ rất tuyệt nếu bổ sung cả khả năng tương thích với NuGet nữa (hay là do tôi không rành về Unity nhỉ?)
Không được hỗ trợ chính thức, nhưng có một dự án mã nguồn mở tên là NuGetForUnity.
Về mặt lý thuyết, các gói Nuget nhắm tới .NET Standard 2.0 cũng có thể được nạp và sử dụng trong môi trường Unity... nhưng có vẻ vẫn có khá nhiều điểm bất tiện.
https://learn.microsoft.com/ko-kr/dotnet/…
Nói thì đúng thật, nhưng tôi không hiểu lắm tại sao lại nhất quyết đem hiệu năng của editor ra so sánh... Ít nhất thì cũng nên mang bản build debug đến để so sánh chứ? Hay là không, làm vậy thì lại càng kém thuyết phục hơn sao? Mặt khác, cũng có cảm giác cả IL2CPP lẫn Mono đều là những công nghệ lỗi thời như nhau.
Trong các dự án lớn, hiệu năng của editor làm giảm đáng kể trải nghiệm phát triển, nên hiệu năng editor cũng rất quan trọng. Việc khởi động editor chậm, import asset cũng chậm, vòng lặp debug/test cũng chậm...
À... tất nhiên điều này cũng quan trọng. Khi tôi đọc lần đầu, có vẻ như tác giả muốn bàn về tốc độ thực thi mã ở mức căn bản hơn. Đúng như bạn nói, việc Unity chậm ở editor, chậm khi import và vòng lặp kiểm thử nhìn chung cũng chậm đều là sự thật...
Thật vui khi thấy một bài viết liên quan đến Unity.
Tôi đã đọc rất thích.
Nếu được áp dụng thành công, có lẽ việc tối ưu hóa cho vô số game indie sẽ được cải thiện.
Tôi nghĩ một lý do nữa khiến Mono nhất định phải được hiện đại hóa bằng CoreCLR là Unity có lẽ không có nhiều điều kiện hay ý chí để đầu tư vào việc cải thiện hiệu năng của Mono. Tôi cho rằng di sản từ thời .NET Framework nên được dọn dẹp càng sớm càng tốt. :-D
Và tôi nghĩ cũng nên cân nhắc rằng, kể từ .NET 10, vấn đề mà trước đây họ muốn giải quyết bằng IL2CPP đang được xử lý một cách chính xác, dù theo hướng phát triển khác, thông qua Native AOT.
Tất nhiên, hạn chế ở đây là nó không tạo ra mã C++ có thể chỉnh sửa ở giữa quá trình, nhưng xét cho cùng thì việc tạo ra native binary không phát sinh Just-In-Time đã trưởng thành hơn nhiều từ .NET 8 và đến .NET 10 thì càng hoàn thiện hơn nữa.
Vì lý do đó, tôi cho rằng việc tiếp tục trì hoãn hiện đại hóa sang CoreCLR sẽ không phải là một lựa chọn tốt đối với Unity. Hoặc cũng có thể việc chuyển hẳn sang một ngôn ngữ hay nền tảng hoàn toàn khác sẽ là phương án hiệu quả hơn!
> Unity có lẽ không có nhiều điều kiện hay ý chí để đầu tư vào việc cải thiện hiệu năng của Mono
Tôi cũng cực kỳ đồng ý với điều này...
Ý kiến trên Hacker News
Có một vài chỗ trong bài viết cho thấy tác giả dường như không có nhiều kinh nghiệm phát triển với Unity
Tóm lại, lý do các lập trình viên Unity mong chờ bản cập nhật này không phải chủ yếu vì hiệu năng mà là vì khả năng tiếp cận các tính năng ngôn ngữ hiện đại. Và việc giảm GC lúc runtime xuống mức tối thiểu hoặc lách qua bằng bộ nhớ unmanaged và DOTS là điều khá phổ biến
IL2CPP chẳng qua chỉ là một trình sinh mã chất lượng thấp chuyển .NET IL sang C++, rồi dựa vào trình biên dịch tối ưu hóa
Có thể thấy điều này qua bài blog của Unity về kiến trúc bên trong IL2CPP
Burst/HPC# cũng chạy theo các xu hướng như ECS hay SoA, nhưng hiệu năng vẫn thua C++ được viết tốt hoặc C# CoreCLR
Hơn nữa, các công nghệ này đều đóng và chỉ dành cho Unity, bên ngoài không dùng được. Unity luôn marketing bằng benchmark so với Mono chậm chạp
Cuối cùng Unity cũng sẽ buộc phải chấp nhận CoreCLR, và khi đó họ sẽ nhận ra một sự thật là mã C# thông thường lại chạy nhanh hơn đống mã phức tạp hiện có
Lý do không dùng IL2CPP là vì nó không tương thích với việc nạp DLL lúc runtime, reflection, đóng gói struct bằng FieldOffset v.v.
Các modder có thể mở rộng tính năng bằng IL injection, nhờ vậy tốc độ phát triển nhìn chung nhanh hơn
Tôi không thích Burst và HPC# vì chúng có nhiều độ phức tạp và ràng buộc. Sự khác biệt hiệu năng giữa Mono và .NET lại càng gây bực bội hơn
Việc profiling trong editor cũng hữu ích vì nó cho thấy mức cải thiện hiệu năng theo tỷ lệ khá giống với bản build thực tế. Tuy nhiên profiler mặc định của Unity không chính xác nên chúng tôi dùng hệ thống tracing tự xây dựng
GC vẫn là vấn đề. Xử lý chuỗi hay UI đều tạo ra rác mỗi frame. Khi chuyển sang CoreCLR, API tốt hơn và GC di chuyển sẽ giúp giảm vấn đề phân mảnh bộ nhớ
Asset Store rất tuyệt, nhưng bản thân engine thì có cảm giác chưa được gọt giũa kỹ.
Scripting dựa trên Mono vốn đã phức tạp về mặt cấu trúc nếu muốn chuyển sang CoreCLR
Nếu Unity thực sự muốn cải thiện phần lõi thì họ phải thiết kế lại toàn bộ editor như Blender 3.x.
Hiện tại nó cho cảm giác như UI từ năm 1999
Vô số plugin và công cụ dừng lại ở giai đoạn “0.x-preview”, rồi sau 5~10 năm thì либо không chạy nữa hoặc bị chôn vùi dưới các asset mới
Vì vậy giờ tôi chỉ dùng các phiên bản từ 1.0 trở lên. Nếu không thì sẽ phụ thuộc vào plugin bị bỏ rơi và cuối cùng lại phải port lại
Unity, lập trình viên lẫn người dùng đều thiệt
Việc thất bại trong phát triển game nội bộ khiến họ thiếu cảm giác thực tế về việc làm game
Họ chỉ thêm các tính năng được yêu cầu, chứ không có tầm nhìn nhất quán
Nếu hiệu năng quan trọng thì nên gọi trực tiếp Vulkan, còn nếu tính di động quan trọng thì nên dùng WebGPU
Mỗi trình duyệt lại có cách triển khai khác nhau nên phát sinh overhead, nhưng nếu WebGPU được cung cấp ở cấp driver của OS thì có thể giải quyết được
Trong khi đó Godot cung cấp các khối xây dựng đơn giản nhưng có ý nghĩa, để bạn tự do tạo ra thứ mình muốn
Asset Store cũng vậy, rất khó bảo trì vì vấn đề tương thích phiên bản, và phần lớn trở thành asset bị bỏ hoang
Khi Unity mua lại các asset hữu ích, họ cũng chẳng tích hợp cho ra hồn, còn các asset cạnh tranh thì biến mất
Trong khi đó Unreal Engine cung cấp những thứ này ở mức tích hợp sẵn trong engine
Unity cũng không có kế hoạch đưa GC tốt hơn vào IL2CPP
Khi editor dựa trên CoreCLR xuất hiện thì thậm chí editor có thể còn nhanh hơn build
Thảo luận liên quan: Hiện đại hóa Unity CoreCLR và .NET
Nếu incremental GC hoạt động tốt thì vấn đề stutter cũng không quá lớn
Vì C# về cơ bản đã nhanh hơn rất nhiều, Unity nhất định phải dồn toàn lực cho quá trình chuyển đổi này
Đội của chúng tôi mất vài tháng để chuyển từ .NET Framework 4.7.2 sang .NET 6, và sau đó việc nâng cấp lên các bản LTS chỉ còn mất vài giờ
Nó cứ tiếp tục bị trì hoãn, còn các lãnh đạo thì rời đi
Tôi đề xuất thay thế bằng engine Stride dựa trên .NET 10. Nó không có overhead qua biên giới như Unity
Godot là mã nguồn mở nhưng hỗ trợ C# thiếu ổn định, và nếu không build được cho Web thì không phù hợp cho game jam
Chúng ta cần một giải pháp sandbox thực sự có hỗ trợ GPU
Ưu tiên cứ thay đổi liên tục, yêu cầu cũng bị sửa đi sửa lại nên công việc cứ lặp lại
Các lập trình viên vẫn rất giỏi, nhưng thiếu động lực triển khai nhất quán
Với CEO, một cuộc đại tu quy mô lớn như vậy cũng là quyết định đầy rủi ro
Kết quả là hiệu năng tăng lên đáng kể, và nhờ giảm phụ thuộc vào engine, việc bảo trì mã cũng dễ hơn
Việc chỉ để lộ các khái niệm của Unity ở những nơi thực sự cần và dùng test để ép ranh giới đã khiến tôi cảm nhận rõ giá trị của sự tách biệt logic
Nếu có quyền root và kết hợp với các công cụ mạng như WireGuard hay Tailscale thì nó cũng hoàn hảo như một máy chủ di động
Với GC mới của .NET 10, hiện tượng giật khựng trong game gần như sẽ biến mất
Hiện tại tôi stream game từ PC chính sang điện thoại bằng Sunlight + Moonlight.
Nhờ màn hình OLED tần số quét cao nên mức tiêu hao pin cũng thấp
Không phải .NET SDK, nhưng vì runtime Mono được đóng gói trong app nên cảm giác cũng gần giống vậy
Lợi thế đa nền tảng của Mono giờ đã không còn nữa, vậy tại sao họ vẫn duy trì mớ hack phức tạp như IL2CPP
Hàng năm trời các chỉnh sửa phi tiêu chuẩn đã tích tụ lại, nên nếu không phải tập đoàn lớn thì cũng khó mà tối ưu lại từ đầu
Với một dự án cỡ này, tôi nghĩ 1~2 năm là đủ