To be honest, Mono isn’t that bad. It has gotten a lot better over the past years. Under the stewardship of Xamarin it has become a viable platform for mobile app development, one that compares very favorably to the native Java and Swift development kits for Android and iOS. I agree that C# is also perfectly fine for developing indie games, with MonoGame for instance. And with Microsoft now gradually merging .NET and Mono together, things can only get better for both frameworks. Unfortunately, because of the aforementioned licensing issues, Unity has reaped none of these benefits over the past 6 years or so, and it’s becoming nigh impossible for them to play catch-up now.
Unity’s biggest performance issues do indeed stem from its outdated architecture and some backward design decisions. Despite all the recent efforts into improving parallelism under the hood, Unity still relies heavily on a single thread. And because of the way scenes are constructed in Unity, with lots of redundant game objects and components, larger more complex maps quickly clog up the CPU and create massive performance bottlenecks. I did some performance optimization work for a Unity-based game about a year ago; one of my biggest contributions consisted of creating a system to clump batches of game objects together. Not for static mesh batching - Unity already does an admirable job at that - but simply to reduce the number of individual game objects in the scene. Or more accurately: to reduce the average number of game objects per mesh vertex. This was based on the observation that while each individual game object has very little overhead, if you have 100k of them in a single scene, with many of them just to hold a single tuft of grass along with 3 LOD levels, that tiny overhead does add up to a significant amount of CPU cycles. The result: a 30% increase in performance across the board. While it was a cool moment of triumph for myself, the fact that you have to even concern yourself with things like this in Unity is madness.
Epic was smart in dropping most of Unreal Engine 3 and starting off with a clean slate for Unreal Engine 4. It allowed them to ditch a lot of accumulated cruft and reconsider all of their design choices within the context of modern hardware and game development practices. Unity would be wise to do the same, but they would risk alienating their current userbase with such a move and I don’t think that’s a risk they are willing to take.
Another pet peeve of mine is the programming style that Unity inevitably leads you towards. I love C# for its high-level productivity features: LINQ, async-await, auto-implemented properties, that sort of thing. But when performance becomes an on a game project (and usually it will), a simple thing like a foreach-loop becomes forbidden, because it heap-allocates an Iterator object in the background, and we don’t want to pressure the GC with unnecessary allocations. Next up, indexing a List<> inside of a regular for-loop is bad because List<T> implement the IList interface so indexing is a virtual function call which is slow (yes, these are discussions that I’ve actually had). So then we’re forced to use plain old arrays, without any of the safety features that you get from using container classes. Before you know it, you’re programming C# like it’s plain old C, but with none of the strengths of either language. And with it comes a whole host of bugs that C# was actually intended to avoid.
In contrast, range-based for-loops in C++ use iterators allocated on the stack, which automatically disappear when they go out of scope, and no additional memory management is needed. Indexing a TArray<> in Unreal compiles right down to a regular array access, so no impact on performance there, but no need to sacrifice safety either. C++ may not be the easiest or most productive language to work with, but at least it’s an honest language with very little hidden costs.