Let me ask you a question: “Which programming language is faster: C++ or C#?”
You probably answered C++. In most cases, it would be correct.
Why is that? C++ is compiled directly to machine code (or assembly – depending on the compiler) and such code can be then executed directly. Compiled C++ program will most likely run as expected on “similar” machines. So, if you have built your program on a Windows 10 machine, it should run without problems on other Windows 10 machines.
Of course, there might be an issue with incompatible libraries. But the real problems start with different architectures such as embedded systems or different operating systems such as MacOS, Linux distributions etc. In this case, you would have to compile your code again specifically for the selected machine.
The general rule is that compiled program works best on the machine it was built on. On the other hand, the situation with C# is quite different. Source code is first compiled into a so-called Common Intermediate Language (CIL). CIL is then compiled by a just-in-time (JIT) compiler during runtime. A very important part of this process is to have a Common Language Runtime (CLR) installed on your machine that will do all required JIT compilations, optimizations, garbage collecting etc. CLR is typically a part of the .NET framework. I guess that by now you can probably tell that these are additional steps that are slowing down the startup time and overall runtime performance.
Windows OS is no longer a limitation
For some time, this paradigm was set in stone and you really couldn’t do much about it. If you wanted to use .NET, then your target platform had to be Windows and you had to settle with the need for the .NET framework being installed and your code being compiled during runtime by JIT. But now these limitations are slowly fading away.
You are no longer required to have your application run on a Windows machine. With .NET Core, you can easily run your application on Linux and MacOS as well. You can even use C# to create mobile applications with Xamarin/MAUI. In 2020 next version of .NET Core 3.1 was named .NET 5 to emphasize that this is now the main implementation of .NET and that Core is not a separate entity.
So now, we are no longer limited by Windows OS. What we are left with is a just-in-time compilation and a requirement for .NET runtime being installed.
Self-contained application: you don’t need runtime installed
As I already mentioned, .NET applications are dependent on .NET runtime being installed on the machine. This is the default state of every .NET application. Such application is called “framework-dependent”. But what if for some reason target machine does not have a runtime installed? Maybe you are not even allowed to install the runtime. Or maybe runtime is installed but not the version you need.
In my experience, it turned out to be quite tricky to have multiple versions of .NET runtime at the same time on Linux. If any of these cases apply to you, there is a handy solution for that (providing you are making your application in .NET Core 2.1+). The solution is called self-contained application. As the name suggests, the runtime is packed with your application so it’s no longer necessary to have it installed. The application is simply carrying runtime with itself.
To do so, the application has to be published with this command: dotnet publish –r
You can see that the only addition to the standard publish command is the usage of parameter –r, which specifies runtime. If we would want to get executable for Windows, we could call it like this:
- dotnet publish –r win-x64
for 64bit operating system or
- dotnet publish –r win-x86
for 32bit operating system.
As a result, we would get executable for a specified platform, in this case .exe file along with .dll that would contain all required dependencies.
Here you can find a full list of runtime identifiers.
Whether you decide to use this approach or not, you should be aware of its advantages and disadvantages.
- You are in control of which version of .NET is deployed with your application.
- You are not dependent on the correct installation of runtime on your target device.
- Application can’t be run on platform, you didn’t expect. That will help you mitigate unexpected behaviour of your application.
- Due to your application having .NET runtime included, the overall size of your build is increased
- Updating .NET runtime of your application requires making a new release of the application.
The downside of self-contained publishing: JIT compilation
With the usage of self-contained publishing, you managed to make your application independent of the target machine to have the runtime installed but the performance of your application remained the same. At the beginning of the article, I mentioned that programs written in C# are somewhat slower than their counterparts mainly because of just-in-time compilation. JIT compiles methods just before they are being called for the first time. This rewards you with less memory usage as only the methods that are required during runtime are compiled into machine code but at the same time, you are punished by a longer start-up.
Luckily after the first longer call, consecutive calls are faster. JIT compilation certainly has its reasons why it exists. For example, Intermediate Language code allows for the program to be multiplatform. The program is tied closely to the target platform only after its compilation to machine code. But before that happens, the platform can be still decided.
Now, if performance drawbacks of JIT compilation are something that concerns you, there is a solution for that as well.
JIT compilation alternative
If you are interested in finding out how you could make your program to perform better especially in terms of start up time, then stay tuned for the second part of this article where I will talk about Ahead of time compilation as a way of making your program to start up and run faster.
Thank you for reading this article.
Part two coming soon.
Senior .NET developer