Quote:
Originally Posted by Disconnect
Certainly, a proper written C++ program will always overtake a proper written C# programs in terms of performance. Just the fact that managed code is being "translated" into machine code at runtime underlines that.
The question is not if managed code uses more resources, but if the performance difference is as significant as you mentioned. Managed code also has advantages like the eliminating of memory leaks.
|
Even if your managed code is compiled into machine code. While things like language-enforced GC make traditional memory leaks go away, it does introduce new memory leaks because it's a leaky abstraction. I've seen many a C# and Java application that had huge memory leaks -- because not calling a very specific API in many cases results in the GC keeping objects marked as in-use even though the code that used them has long since renounced any need to do so. It's not a perfect system but it does let people program without worrying about issues like that so development is easier.
But when you want performance, you don't want to go to a heap manager for everything, and you do not want your objects collected by a garbage collector. Even as raw machine code, needing to jump through hoops like this results in severe performance loss. An excellent C# or Java compiler may be smart enough to optimize a locally used object to be allocated on the stack, but in almost all cases they don't since this inherently breaks the ability to guarantee stack overflows and stack corruption don't happen, and it makes it impossible to do accounting on the objects. Heap allocations are SLOW, if you have 20,000 people connected to your server and you need to do 10 calls per each message sent/received per client, and each is sending about 1 thing each second, you're talking about 200,000 calls to a heap manager every second. That's a significant amount of CPU time.
Consider this program:
Code:
#include <stdio.h>
#include <Windows.h>
void foo(){
int* p = new int;
*p = 5;
delete p;
}
void bar(){
int p = 5;
}
int main(int argc, char** argv){
DWORD x = GetTickCount();
for(int i=0;i<200000;i++){
foo();
}
DWORD y = GetTickCount();
printf("new/delete took %d msec!\n",y-x);
x = GetTickCount();
for(int i=0;i<200000;i++){
bar();
}
y = GetTickCount();
printf("stack took %d msec!\n",y-x);
return 0;
}
Running it with a 2.6GHz processor on 1 thread results in these outputs:
Code:
new/delete took 203 msec!
stack took 0 msec!
Granted if your system in C# or Java is well designed, you'll have multiple threads handling packets, so that will be divided by however many cores you have on your system, its maximum concurrency level. It's still several orders of magnitude slower, and 10 is a VERY conservative estimate for how many heap mgr calls your code will intrinsically make, and it doesn't account for GC cycles which are in general VERY slow.
You can do things in C/C++ easily like pooling allocated memory into an allocator that can use a constant-time return and leak checks, even a ptr table and ref counts to fix leaks automatically, at the cost of performance, so you get as close to stack allocation as possible while still having managed memory objects, with the same new/delete style interface. If you can do that at C# or Java at all, it requires being very clever to force the language/compiler to do what you want. We also have common idioms to fix things like this, called ref counted smart pointers which let you wrap a persistent object, likely allocated from some pool, in a stack-based object that respects scope and exception safety which will automatically call your pool-deallocator on the object when scope is up or an exception is dispatched, interrupting the flow of execution, based on a reference count. This is a beautiful solution and is applicable in most situations. As always, if it's not threadsafe, it's VERY fast, but with thread safety comes mutual exclusion or live waiting for lock-free designs, in any case you lose performance in general.
It's inherent in how your language works that makes it less efficient, it's not about the code you write. Algorithmic efficiency is a very important factor, but assuming everyone uses the same efficient algorithms, it comes down to language artifacts and inefficacies like this.