(转载)C#比C++慢吗?
作者:互联网
Is C# Slower Than C++?
C# / March 16, 2020
Is C# slower than C++? That’s a pretty big question. As a junior developer, I was sure that the answer is “Yes, definitely”. Now that I’m more experienced, I know that this question is not obvious and even quite complicated.
Before trying to answer this, here’s another question: “Does it really matter?”. With modern CPUs, development productivity should be much more important than performance, right? And writing in C# is much more productive than C++, so it’s not like we’re going to switch to C++ just because it’s a little faster.
I think it does matter though. Knowing the answer in detail will allow to improve both languages. You might learn how to write more performant C# code. Or we might improve the C++ language and its libraries to be more productive. Or you might move your performance-sensitive hot paths to C++, calling this code from C# via interop. Alternatively, if both languages are just as performant, you might avoid a lot of unnecessary work switching to C++ in the first place. Besides all that, answering this is a pretty fun exercise.
Managed code vs Native code
Comparing C# and C++ leads to a more general question: “Is managed code slower than native code?”. What’s the difference between those two anyway? There are several differences. One of them is that Native code like C or C++ is compiled directly into machine code, which can be executed by the computer. Managed code, on the other hand, is compiled into an “intermediate” code first. That’s byte-code in Java and Intermediate Language (IL) Code in C#. Then, during runtime, the Just In Time (JIT) Compiler compiles that code into machine code. The reason for this is that the same code can be compiled differently on different machines. So, for example, the same IL Code can be compiled both to Windows and to Linux.
Another big difference is memory management, but let’s talk start with the implications of IL Code first.
IL Code vs Machine Code
In managed code, there’s another compilation stage during runtime. IL Code compiling to machine code. So does that mean managed code is always going to be slower than native code? Well, by default, yes. The first time a method is called in C#, it will be JIT-compiled to machine code. That takes time. But, it only happens once for each method. That’s why startup times of a .NET process are going to be higher than those of a C++ process. Well, at least one of the reasons for it.
Startup time is not such a huge issue though, and here’s why: In a server, startup time doesn’t really play a role. Servers run for a very long time and it’s negligible. And even if you deploy several times a day, you can deploy to a staging environment, wait for startup to finish, and then “swap” the staging and production on the DNS level.
As for desktop applications and mobile, startup time is a pretty big deal indeed. However, this is solved by a technique called Ahead of Time (AOT) Compilation. This is where you compile the IL Code into machine code before running it. In C#, it’s done with a tool called Ngen.exe, which should be executed on the user’s machine. It’s usually another step in the installer. For mobile, JIT compilation is just not an option. There are device limitations that don’t allow it, so AOT compilation is necessary. That’s something already done in the Mono runtime and was added to .NET Core with a technology called ReadyToRun Images (R2R).
So if JIT compilation is not an issue, then both C# code and C++ code should run at the same speed, right? After all, it’s the same machine code instructions. The JIT compilation issue is just one part of it though. Which code will produce better machine code do you think? C++ code compiled straight into machine code? Or C# code compiled into IL Code and then to machine code? Which will be more optimized and have fewer instructions?
Let’s assume that both C# compilers and C++ compilers are optimized brilliantly. In reality, I think this is pretty close to the truth. Now consider C# code practices. We constantly use things like LINQ, the async/await state machine, pattern matching, and reflection. Those are all useful and productive features, but also slow. In C++, we mostly write code much closer to machine code. We work directly with memory pointers and have little high-level abstractions. So even though it’s possible to write low-level code in C# and high-level code in C++ (there are libraries for everything now, including C++ LINQ), mostly C++ code will produce faster machine code with fewer instructions.
In conclusion, you can, in theory, create C# code that’s just as fast as C++ code. But, in most cases, C++ code is going to be faster because of coding habits. The differences usually don’t really matter, but they do matter in hot paths and algorithms. So as a C# developer, make sure to optimize the hell out of performance-sensitive code.
Memory Management
Another aspect of managed code is memory management. In a managed language like C#, the Common Language Runtime (CLR) is fully responsible for memory. It will find memory space for each allocation and automatically remove memory when it’s no longer referenced. This is called Garbage Collection. The Garbage Collector also moves memory around to prevent fragmentation issues. That’s when you allocate and remove memory many times until you have “holes” in the memory buffer. This makes it slow to allocate new memory and can eventually cause out-of-memory crashes, even though there’s plenty of free memory. In C#, the garbage collector constantly moves memory buffers next to each other. This way, it knows exactly where to allocate memory and doesn’t need to look for a free “hole” (though fragmentation problems still exists for the large object heap)
In a native language, the allocations and deallocations are controlled by the programmer. A new
statement allocates code directly on the heap, and a delete
frees the memory. Since memory doesn’t ever “move” like in .NET, this works well. Except that it creates a whole lot of room for human error. If you fail to delete memory, it will be taken forever and cause a memory leak. Have enough memory leaks and you’ll start having performance issues and eventually crash. But we’re talking in theory here, so let’s assume all C++ programmers create perfect code that never has memory leaks.
So which is faster, C++ code or C# code? Well, garbage collection, although wonderful, takes time. During garbage collection, threads usually have to pause execution. So there’s code that runs in your process executing garbage collection logic instead of executing user code. Having said that, the garbage collector is extremely optimized. In recent versions of the GC, it’s even able to run in the background on another thread, not hurting performance at all (well, sometimes).
So the question to ask is whether the garbage collection overhead is going to hurt performance more than the fragmentation problem overhead? In the short term, when an application’s just started to run, the fragmentation is not a problem and garbage collection certainly is. So C++ is definitely faster at the program start. In the long term, when your app runs for hours and days on end, the fragmentation issue is going to catch up. Allocations will become slower, and in some cases, it will lead to crashes.
C++ developers know of this issue and have ways to deal with it. One solution is custom allocators, which can reuse memory buffers or allocate smartly to minimize fragmentation issues.
Another solution is to allocate on the Stack instead of the Heap as much as possible. Stack memory has no fragmentation issues by definition, as well as a couple of added benefits. Using objects on the stack is much faster than the heap. There’s no need to read a memory reference and then go to that place for the actual object. And Stack memory works better because of CPU cache. Another benefit is that you don’t have to manually delete the object, so there’s no place for human error.
Allocating on the stack works great in C++, but not so much in C#. In C++, you can allocate any object on the stack, whether a class or struct. You can easily pass stack-allocated objects by reference. In C#, the type defines whether you allocate on the heap or the stack. Value-types like structs are allocated on the stack whereas reference-types like classes are allocated on the heap. Collections like arrays and List
are allocated on the heap. Although as of C# 7.3 you can use the new stackalloc
keyword to allocate arrays on the stack and pass them by reference. The Stack vs Heap issue is so important in terms of performance that the latest C# versions are heavily invested into allowing more stack allocations and passing those objects by reference. Still, C++ is much more suited for this with its pointers.
So, C++ is better at stack allocation and has no garbage collection overhead. C#, on the other hand, has no fragmentation problems. Productivity issues aside, I think C++ is overall faster in this regard. Mainly due to stack allocation and not because of the lack of a garbage collector. C# is catching up a bit, what with the new stackalloc
and Span
capabilities.
Summary
My thought process in this post probably doesn’t describe all aspects of C# and C++ performance. I’m sure there are many more arguments to both sides (and I’d love you to comment and share them). Moreover, when it comes to performance, any argument is just a guess without a benchmark to back it up. In this specific case, it will be incredibly difficult to create the correct benchmarks since there too many different scenarios and things to compare.
Still, my conclusion is that C# is not as fast as C++ in most cases by default. But I think it’s not much slower and it usually doesn’t matter. When you do have performance-sensitive code, you can optimize C# and achieve near-similar performance to C++. This can be done with stack allocations, avoiding LINQ, reusing memory, and many other performance optimizations.
标签:code,C#,C++,machine,memory,performance,转载 来源: https://www.cnblogs.com/dewxin/p/16349786.html