简体   繁体   English

为什么C#比VB.NET更慢地执行Math.Sqrt()?

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

Background 背景

While running benchmark tests this morning, my colleagues and I discovered some strange things concerning performance of C# code vs. VB.NET code. 在今天早上运行基准测试时,我和我的同事发现了一些关于C#代码与VB.NET代码性能的奇怪之处。

We started out comparing C# vs. Delphi Prism calculating prime numbers, and found that Prism was about 30% faster. 我们开始比较C#与Delphi Prism计算质数,发现Prism的速度提高了约30%。 I figured CodeGear optimized code more when generating IL (the exe was about twice as big as C#'s and had all sorts of different IL in it.) 我在生成IL时更多地考虑了CodeGear优化代码( exe大约是C#的两倍,并且有各种不同的IL。)

I decided to write a test in VB.NET as well, assuming that Microsoft's compilers would end up writing essentially the same IL for each language. 我决定在VB.NET中编写一个测试,假设微软的编译器最终会为每种语言编写基本相同的IL。 However, the result there was more shocking: the code ran more than three times slower on C# than VB with the same operation! 然而,结果更令人震惊: C#的代码运行速度比使用相同操作的VB慢三倍!

The generated IL was different, but not extremely so, and I'm not good enough at reading it to understand the differences. 生成的IL是不同的,但并非极端如此,而且我不太善于阅读它以理解差异。

Benchmarks 基准

I've included the code for each below. 我已经在下面列出了每个代码。 On my machine, VB finds 348513 primes in about 6.36 seconds. 在我的机器上,VB在大约6.36秒内找到了348513个素数。 C# finds the same number of primes in 21.76 seconds. C#在21.76秒内找到相同数量的素数。

Computer Specs and Notes 计算机规格和注释

  • Intel Core 2 Quad 6600 @ 2.4Ghz 英特尔酷睿2四核6600 @ 2.4Ghz

Every machine I've tested on there is a noticeable difference in the benchmark results between C# and VB.NET. 我在那里测试的每台机器在C#和VB.NET之间的基准测试结果上都有明显的差异。

Both of the console applications were compiled in Release mode, but otherwise no project settings were changed from the defaults generated by Visual Studio 2008. 两个控制台应用程序都是在发布模式下编译的,但是否则没有从Visual Studio 2008生成的默认值更改项目设置。

VB.NET code VB.NET代码

Imports System.Diagnostics

Module Module1

    Private temp As List(Of Int32)
    Private sw As Stopwatch
    Private totalSeconds As Double

    Sub Main()
        serialCalc()
    End Sub

    Private Sub serialCalc()
        temp = New List(Of Int32)()
        sw = Stopwatch.StartNew()
        For i As Int32 = 2 To 5000000
            testIfPrimeSerial(i)
        Next
        sw.Stop()
        totalSeconds = sw.Elapsed.TotalSeconds
        Console.WriteLine(String.Format("{0} seconds elapsed.", totalSeconds))
        Console.WriteLine(String.Format("{0} primes found.", temp.Count))
        Console.ReadKey()
    End Sub

    Private Sub testIfPrimeSerial(ByVal suspectPrime As Int32)
        For i As Int32 = 2 To Math.Sqrt(suspectPrime)
            If (suspectPrime Mod i = 0) Then
                Exit Sub
            End If
        Next
        temp.Add(suspectPrime)
    End Sub

End Module

C# Code C#代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace FindPrimesCSharp {
    class Program {
        List<Int32> temp = new List<Int32>();
        Stopwatch sw;
        double totalSeconds;


        static void Main(string[] args) {

            new Program().serialCalc();

        }


        private void serialCalc() {
            temp = new List<Int32>();
            sw = Stopwatch.StartNew();
            for (Int32 i = 2; i <= 5000000; i++) {
                testIfPrimeSerial(i);
            }
            sw.Stop();
            totalSeconds = sw.Elapsed.TotalSeconds;
            Console.WriteLine(string.Format("{0} seconds elapsed.", totalSeconds));
            Console.WriteLine(string.Format("{0} primes found.", temp.Count));
            Console.ReadKey();
        }

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

    }
}

Why is C#'s execution of Math.Sqrt() slower than VB.NET? 为什么C#的Math.Sqrt()执行速度比VB.NET慢?

The C# implementation is recalculating Math.Sqrt(suspectPrime) each time through the loop, while VB only calculates it at the beginning of the loop. C#实现每次循环都会重新计算Math.Sqrt(suspectPrime) ,而VB只在循环开始时计算它。 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. 在C#中, for只是一个奇特的while循环,而在VB中它是一个单独的构造。

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; 
        }

I agree with the statement that the C# code is computing sqrt on every iteration and here is the proof straight out of Reflector: 我同意C#代码在每次迭代时计算sqrt的说法,这里是Reflector的直接证明:

VB version: VB版:

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: C#版本:

 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. 哪一种指向VB生成的代码即使开发人员天真到足以在循环定义中调用sqrt也能表现得更好。

Here is the decompiled IL from the for loops. 这是来自for循环的反编译IL。 If you compare the two you will see VB.Net only does the Math.Sqrt(...) onces while C# checks it on each pass. 如果你比较两者,你会看到VB.Net只执行Math.Sqrt(...) onces,而C#在每次传递时检查它。 To fix this you would need to do something like var sqrt = (int)Math.Sqrt(suspectPrime); 要解决这个问题,你需要做一些像var sqrt = (int)Math.Sqrt(suspectPrime); as other have suggested. 正如其他人所说。

... VB ... ...... 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# ... ... 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

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. 关闭切线,如果你已经启动并运行VS2010,你可以利用PLINQ并更快地制作C#(可能也是VB.Net)。

Change that for loop to... 将for for循环更改为......

var range = Enumerable.Range(2, 5000000);

range.AsParallel()
    .ForAll(i => testIfPrimeSerial(i));

I went from 7.4 -> 4.6 seconds on my machine. 我在机器上从7.4 - > 4.6秒开始。 Moving it to release mode shaves a little more time on top of that. 将其移至发布模式可以节省更多时间。

The difference is in the loop; 不同之处在于循环; your C# code is computing the square root on every iteration. 你的C#代码计算每次迭代的平方根。 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. 把我的机器上的时间从26秒减少到7.something。

Generally no. 一般没有。 They both compile to CLR (Common Language Runtime) byte-code. 它们都编译为CLR(公共语言运行时)字节码。 This is similar to a JVM (Java Virtual Machine). 这类似于JVM(Java虚拟机)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM