Why is a loop in Delphi faster than C #?

Delphi:

procedure TForm1.Button1Click(Sender: TObject); var I,Tick:Integer; begin Tick := GetTickCount(); for I := 0 to 1000000000 do begin end; Button1.Caption := IntToStr(GetTickCount()-Tick)+' ms'; end; 

FROM#:

 private void button1_Click(object sender, EventArgs e) { int tick = System.Environment.TickCount; for (int i = 0; i < 1000000000; ++i) { } tick = System.Environment.TickCount - tick; button1.Text = tick.ToString()+" ms"; } 

Delphi gives about 515 ms

C # gives about 3775 ms

+4
source share
10 answers

Delphi is compiled into native code, while C # is compiled into CLR code, which is then translated at runtime. However, C # uses JIT compilation, so you can expect the time to be more similar, but this is not set.

It would be helpful if you could describe the hardware on which you ran it (CPU, clock speed).

I do not have access to Delphi to repeat your experiment, but using native C ++ vs C # and the following code:

VC ++ 2008

 #include <iostream> #include <windows.h> int main(void) { int tick = GetTickCount() ; for (int i = 0; i < 1000000000; ++i) { } tick = GetTickCount() - tick; std::cout << tick << " ms" << std::endl ; } 

FROM#

 using System; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { int tick = System.Environment.TickCount; for (int i = 0; i < 1000000000; ++i) { } tick = System.Environment.TickCount - tick; Console.Write( tick.ToString() + " ms" ) ; } } } 

I originally received:

 C++ 2792ms C# 2980ms 

However, I then rebuilt the C # version and executed the executable in <project>\bin\release and <project>\bin\debug respectively directly from the command line. It gave:

 C# (release): 720ms C# (debug): 3105ms 

So, I believe that this is exactly the difference: you ran the debug version of C # code from the IDE.

If you think C ++ is then especially slow, I ran this as an optimized version of the assembly and got:

 C++ (Optimised): 0ms 

This is not surprising because the loop is empty and the control variable is not used outside the loop, so the optimizer completely removes it. To avoid this, I declared i as volatile with the following result:

 C++ (volatile i): 2932ms 

I assume that the C # implementation also deleted the loop and that 720ms is something else; this can explain most of the difference between the timings in the first test.

What Delphi does, I cannot say, you can look at the generated assembly code to see.

All of the above tests on AMD Athlon Dual Core 5000B 2.60GHz, on Windows 7 32bit.

+28
source

If this is intended as a reference, this is a very bad result, since in both cases the cycle can be optimized, so you need to look at the generated machine code to find out what is happening. If you use release mode for C #, the following code

  Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < 1000000000; ++i){ } sw.Stop(); Console.WriteLine(sw.Elapsed); 

JITter converts to this:

  push ebp mov ebp,esp push edi push esi call 67CDBBB0 mov edi,eax xor eax,eax ; i = 0 inc eax ; ++i cmp eax,3B9ACA00h ; i == 1000000000? jl 0000000E ; false: jmp mov ecx,edi cmp dword ptr [ecx],ecx call 67CDBC10 mov ecx,66DDAEDCh call FFE8FBE0 mov esi,eax mov ecx,edi call 67CD75A8 mov ecx,eax lea eax,[esi+4] mov dword ptr [eax],ecx mov dword ptr [eax+4],edx call 66A94C90 mov ecx,eax mov edx,esi mov eax,dword ptr [ecx] mov eax,dword ptr [eax+3Ch] call dword ptr [eax+14h] pop esi pop edi pop ebp ret 
+8
source

TickCount not a reliable timer; You must use the .Net Stopwatch class. (I do not know what the Delphi equivalent is).

Also do you use release builds?
Do you have a debugger?

+6
source

The Delphi compiler uses a for down loop counter (if possible); The above code example is compiled for:

 Unit1.pas. 42: Tick := GetTickCount(); 00489367 E8B802F8FF call GetTickCount 0048936C 8BF0 mov esi,eax Unit1.pas.43: for I := 0 to 1000000000 do 0048936E B801CA9A3B mov eax,$3b9aca01 00489373 48 dec eax 00489374 75FD jnz $00489373 
+4
source

this is C # parsing:
DEBUG:

 // int i = 0; while (++i != 1000000000) ;//==for(int i ...blah blah blah) 0000004e 33 D2 xor edx,edx 00000050 89 55 B8 mov dword ptr [ebp-48h],edx 00000053 90 nop 00000054 EB 00 jmp 00000056 00000056 FF 45 B8 inc dword ptr [ebp-48h] 00000059 81 7D B8 00 CA 9A 3B cmp dword ptr [ebp-48h],3B9ACA00h 00000060 0F 95 C0 setne al 00000063 0F B6 C0 movzx eax,al 00000066 89 45 B4 mov dword ptr [ebp-4Ch],eax 00000069 83 7D B4 00 cmp dword ptr [ebp-4Ch],0 0000006d 75 E7 jne 00000056 

As you can see, this is a waste of the processor.
EDIT:
RELEASE:

  //unchecked //{ //int i = 0; while (++i != 1000000000) ;//==for(int i ...blah blah blah) 00000032 33 D2 xor edx,edx 00000034 89 55 F4 mov dword ptr [ebp-0Ch],edx 00000037 FF 45 F4 inc dword ptr [ebp-0Ch] 0000003a 81 7D F4 00 CA 9A 3B cmp dword ptr [ebp-0Ch],3B9ACA00h 00000041 75 F4 jne 00000037 //} 

EDIT:
and this is the C ++ version: it works on my machine about 9 times faster.

  __asm { PUSH ECX PUSH EBX XOR ECX, ECX MOV EBX, 1000000000 NEXT: INC ECX CMP ECX, EBX JS NEXT POP EBX POP ECX } 
+2
source

You are comparing native code with VM JIT code, and this is unfair. The source code will ALWAYS be faster , since JITTER cannot optimize the code, for example, as its own compiler.

However, comparing Delphi with C # is not fair at all , the Delphi binary will always win (faster, less, without any dependencies, etc.).

By the way, I am sadly surprised how many posters here do not know these differences ... or maybe you just hurt some .NET fans who are trying to protect C # from everything that shows that there are better options .

+2
source

You should attach a debugger and look at the machine code generated by each.

+1
source

"// int i = 0, whereas (++ i! = 1000000000);"

It is interesting.

while (++ i! = x) does not match (; i! = x; i ++)

The difference is that the while loop does not execute the loop for i = 0.

(try: run something like this:

 int i; for (i = 0; i < 5; i++) Console.WriteLine(i); i = 0; while (++i != 5) Console.WriteLine(i); 
0
source

Delphi almost certainly optimizes this loop to execute in the reverse order (i.e., DOWNTO zero, not FROM zero) - Delphi does this whenever it determines that it is "safe", apparently because either subtraction, either checking for zero is faster than adding or checking for a non-zero number.

What happens if you try both cases by specifying loops to execute in the reverse order?

0
source

In Delphi, the interruption condition is calculated only once before the start of the loop procedure, while in C # the interruption condition is calculated in each pass of the cycle again.

This is why a loop in Delphi is faster than in C #.

0
source

Source: https://habr.com/ru/post/1307276/


All Articles