Why does C# execute Math.Sqrt() more slowly than VB.NET?

后端 未结 6 1539
情深已故
情深已故 2020-12-14 05:45

Background

While running benchmark tests this morning, my colleagues and I discovered some strange things concerning performance of C# code vs. VB.NET code.

相关标签:
6条回答
  • 2020-12-14 06:00

    The C# implementation is recalculating Math.Sqrt(suspectPrime) each time through the loop, while VB only calculates it at the beginning of the loop. This is just due to the nature of the control structure. In C#, for is just a fancy while loop, while in VB it's a separate construct.

    Using this loop will even up the score:

            Int32 sqrt = (int)Math.Sqrt(suspectPrime)
            for (Int32 i = 2; i <= sqrt; i++) { 
                if (suspectPrime % i == 0) 
                    return; 
            }
    
    0 讨论(0)
  • 2020-12-14 06:04

    The difference is in the loop; your C# code is computing the square root on every iteration. Changing that one line from:

    for (Int32 i = 2; i <= Math.Sqrt(suspectPrime); i++) {
    

    to:

    var lim = Math.Sqrt(suspectPrime);
    for (Int32 i = 2; i <= lim; i++) {
    

    dropped the time on my machine from 26 seconds to 7.something.

    0 讨论(0)
  • 2020-12-14 06:11

    Off on a tangent, if you're up and running with VS2010, you can take advantage of PLINQ and make C# (probably VB.Net as well) faster.

    Change that for loop to...

    var range = Enumerable.Range(2, 5000000);
    
    range.AsParallel()
        .ForAll(i => testIfPrimeSerial(i));
    

    I went from 7.4 -> 4.6 seconds on my machine. Moving it to release mode shaves a little more time on top of that.

    0 讨论(0)
  • 2020-12-14 06:13

    I agree with the statement that the C# code is computing sqrt on every iteration and here is the proof straight out of Reflector:

    VB version:

    private static void testIfPrimeSerial(int suspectPrime)
    {
        int VB$t_i4$L0 = (int) Math.Round(Math.Sqrt((double) suspectPrime));
        for (int i = 2; i <= VB$t_i4$L0; i++)
        {
            if ((suspectPrime % i) == 0)
            {
                return;
            }
        }
        temp.Add(suspectPrime);
    }
    

    C# version:

     private void testIfPrimeSerial(int suspectPrime)
    {
        for (int i = 2; i <= Math.Sqrt((double) suspectPrime); i++)
        {
            if ((suspectPrime % i) == 0)
            {
                return;
            }
        }
        this.temp.Add(suspectPrime);
    }
    

    Which kinda points to VB generating code that performs better even if the developer is naive enough to have the call to sqrt in the loop definition.

    0 讨论(0)
  • 2020-12-14 06:14

    Here is the decompiled IL from the for loops. If you compare the two you will see VB.Net only does the Math.Sqrt(...) onces while C# checks it on each pass. To fix this you would need to do something like var sqrt = (int)Math.Sqrt(suspectPrime); as other have suggested.

    ... VB ...

    .method private static void CheckPrime(int32 suspectPrime) cil managed
    {
        // Code size       34 (0x22)
        .maxstack  2
        .locals init ([0] int32 i,
             [1] int32 VB$t_i4$L0)
        IL_0000:  ldc.i4.2
        IL_0001:  ldarg.0
        IL_0002:  conv.r8
        IL_0003:  call       float64 [mscorlib]System.Math::Sqrt(float64)
        IL_0008:  call       float64 [mscorlib]System.Math::Round(float64)
        IL_000d:  conv.ovf.i4
        IL_000e:  stloc.1
        IL_000f:  stloc.0
        IL_0010:  br.s       IL_001d
    
        IL_0012:  ldarg.0
        IL_0013:  ldloc.0
        IL_0014:  rem
        IL_0015:  ldc.i4.0
        IL_0016:  bne.un.s   IL_0019
    
        IL_0018:  ret
    
        IL_0019:  ldloc.0
        IL_001a:  ldc.i4.1
        IL_001b:  add.ovf
        IL_001c:  stloc.0
        IL_001d:  ldloc.0
        IL_001e:  ldloc.1
        IL_001f:  ble.s      IL_0012
    
        IL_0021:  ret
    } // end of method Module1::testIfPrimeSerial
    

    ... C# ...

    .method private hidebysig static void CheckPrime(int32 suspectPrime) cil managed
    {
        // Code size       26 (0x1a)
        .maxstack  2
        .locals init ([0] int32 i)
        IL_0000:  ldc.i4.2
        IL_0001:  stloc.0
        IL_0002:  br.s       IL_000e
    
        IL_0004:  ldarg.0
        IL_0005:  ldloc.0
        IL_0006:  rem
        IL_0007:  brtrue.s   IL_000a
    
        IL_0009:  ret
    
        IL_000a:  ldloc.0
        IL_000b:  ldc.i4.1
        IL_000c:  add
        IL_000d:  stloc.0
        IL_000e:  ldloc.0
        IL_000f:  conv.r8
        IL_0010:  ldarg.0
        IL_0011:  conv.r8
        IL_0012:  call       float64 [mscorlib]System.Math::Sqrt(float64)
        IL_0017:  ble.s      IL_0004
    
        IL_0019:  ret
    } // end of method Program::testIfPrimeSerial
    
    0 讨论(0)
  • 2020-12-14 06:23

    Generally no. They both compile to CLR (Common Language Runtime) byte-code. This is similar to a JVM (Java Virtual Machine).

    0 讨论(0)
提交回复
热议问题