Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why the performance difference between C# (quite a bit slower) and Win32/C?

We are looking to migrate a performance critical application to .Net and find that the c# version is 30% to 100% slower than the Win32/C depending on the processor (difference more marked on mobile T7200 processor). I have a very simple sample of code that demonstrates this. For brevity I shall just show the C version - the c# is a direct translation:

#include "stdafx.h"
#include "Windows.h"

int array1[100000];
int array2[100000];

int Test();

int main(int argc, char* argv[])
{
    int res = Test();

    return 0;
}

int Test()
{
    int calc,i,k;
    calc = 0;

    for (i = 0; i < 50000; i++) array1[i] = i + 2;

    for (i = 0; i < 50000; i++) array2[i] = 2 * i - 2;

    for (i = 0; i < 50000; i++)
    {
        for (k = 0; k < 50000; k++)
        {
            if (array1[i] == array2[k]) calc = calc - array2[i] + array1[k];
            else calc = calc + array1[i] - array2[k];
        } 
    }
    return calc;
}

If we look at the disassembly in Win32 for the 'else' we have:

35:               else calc = calc + array1[i] - array2[k]; 
004011A0   jmp         Test+0FCh (004011bc)
004011A2   mov         eax,dword ptr [ebp-8]
004011A5   mov         ecx,dword ptr [ebp-4]
004011A8   add         ecx,dword ptr [eax*4+48DA70h]
004011AF   mov         edx,dword ptr [ebp-0Ch]
004011B2   sub         ecx,dword ptr [edx*4+42BFF0h]
004011B9   mov         dword ptr [ebp-4],ecx

(this is in debug but bear with me)

The disassembly for the optimised c# version using the CLR debugger on the optimised exe:

                    else calc = calc + pev_tmp[i] - gat_tmp[k];
000000a7  mov         eax,dword ptr [ebp-4] 
000000aa  mov         edx,dword ptr [ebp-8] 
000000ad  mov         ecx,dword ptr [ebp-10h] 
000000b0  mov         ecx,dword ptr [ecx] 
000000b2  cmp         edx,dword ptr [ecx+4] 
000000b5  jb          000000BC 
000000b7  call        792BC16C 
000000bc  add         eax,dword ptr [ecx+edx*4+8]
000000c0  mov         edx,dword ptr [ebp-0Ch] 
000000c3  mov         ecx,dword ptr [ebp-14h] 
000000c6  mov         ecx,dword ptr [ecx] 
000000c8  cmp         edx,dword ptr [ecx+4]
000000cb  jb          000000D2 
000000cd  call        792BC16C 
000000d2  sub         eax,dword ptr [ecx+edx*4+8] 
000000d6  mov         dword ptr [ebp-4],eax 

Many more instructions, presumably the cause of the performance difference.

So 3 questions really:

  1. Am I looking at the correct disassembly for the 2 programs or are the tools misleading me?

  2. If the difference in the number of generated instructions is not the cause of the difference what is?

  3. What can we possibly do about it other than keep all our performance critical code in a native DLL.

Thanks in advance Steve

PS I did receive an invite recently to a joint MS/Intel seminar entitled something like 'Building performance critical native applications' Hmm...

like image 608
Steve Avatar asked Jun 29 '09 19:06

Steve


2 Answers

I believe your main issue in this code is going to be bounds checking on your arrays.

If you switch to using unsafe code in C#, and use pointer math, you should be able to achieve the same (or potentially faster) code.

This same issue was previously discussed in detail in this question.

like image 148
Reed Copsey Avatar answered Sep 27 '22 20:09

Reed Copsey


I believe you are seeing the results of bounds checks on the arrays. You can avoid the bounds checks by using unsafe code.

I believe the JITer can recognize patterns like for loops that go up to array.Length and avoid the bounds check, but it doesn't look like your code can utilizate that.

like image 37
Michael Avatar answered Sep 27 '22 21:09

Michael