Earlier quoted context omitted.
I do extensive profiling of managed apps and while the JIT does eat a measurable amount of CPU time, it's really not much. And at least on .NET, you can manually ask the runtime to JIT methods, so you can go 'okay, it's startup time, I'm going to spin up a pool of threads and use them to JIT all my code' without blocking execution - for a game I'm working on it takes around 2 seconds to warm ~4000 methods while the o…
I 've never understood the point of JITing. Just compile the thing once for your target architecture and you're done. No more spawning threads for doing the same thing over and over again. I'm glad that languages like Go and Rust are bringing back the lost simplicity of yesteryear's dinosaur languages. Life can be so easy.
FBOW it's worth remembering the 'cruft' and legacy around .NET's Architecture designs and choices, even if some of the assumptions were wrong.
1. .NET seems to have been originally built under a heavy assumption that the framework was installed on a device. This could be a huge advantage for smaller/embedded devices of the time, if you were working 'close to framework' (as was the style at the time, NuGet wasn't a thing for the better part of a decade after .NET was introduced) you could get a surprisingly compact deployment object. Many of the 'in-house' apps I wrote with clickonce, the cert validation seemed to take longer than the network transfer on upgrade.
1.1: VERY worth noting, the idea that .NET browser plugins would compete with Java plugins; JIT is probably important to do this sanely.
2. Speaking of assumptions, I'm willing to bet that around the design time of many .NET particulars, they were still reeling from the pains of both Win16->Win32 breakages (Win95, Win98), Win16->NT breakages (NT4.0, 2K), Win32/WinNT breakages (XP), and X86/X64 breakages (Server 2003). JIT, alongside a framework API with strongly contracted public members [^0] allows problems to be fixed without vendors necessarily having to provide updated libraries [^1]
3. CAS (Code Access Security) and 'Partially Trusted code'. This somewhat ties back into 1.1, because the idea was that code had to have a certain level of 'verification' around what it was doing, what libraries it was calling, and whether that code -should- be allowed to execute such on a box. I'm thinking of scenarios where an 'intranet' app could have access to a local MSSQL database and open/write files, but an 'internet' app could not. Implementing such via a JIT is far simpler.
4. The combination of Generics and reflection complicate things. To be more specific, the way .NET handles generic methods, basically any reference types (objects) will share the code, as the size of the generic parameter(s) is going to be the size of a reference on the target platform. But in the case of a Value Type (structs), I'm 99% the runtime requires specific code for every different struct[^2] type that is slotted into one of the generic types of a class/method.
5. Simpler dynamic-ish code generation. One of the most lovely (if unloved/ignored) APIs in .NET is the Expressions namespace.
[^0] By this I mean there is a -strong- slant towards .NET APIs retaining existing behavior even if it is wrong/subpar, if that behavior is part of the accepted contract.
[^1] How well this worked out in practice, probably not so much. I remember my first experience with NET 1.0/1.1 versioning/deployment hell and going back to C++ for a few years. It wasn't until my apps had semi-complex UIs (i.e. not console and multi-window) alongside the renaissance of 3.5 that .NET became a more efficient workflow than the sometimes daunting MFC/ATL C++ winforms workflow.
[^2] AFAIK the runtime cannot share struct implementations between two structs with the same layout (I -think- go generics can based of GCShape but maybe not, I'm not a gopher,) but would be delighted to hear otherwise.