簡體   English   中英

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

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

背景

在今天早上運行基准測試時,我和我的同事發現了一些關於C#代碼與VB.NET代碼性能的奇怪之處。

我們開始比較C#與Delphi Prism計算質數,發現Prism的速度提高了約30%。 我在生成IL時更多地考慮了CodeGear優化代碼( exe大約是C#的兩倍,並且有各種不同的IL。)

我決定在VB.NET中編寫一個測試,假設微軟的編譯器最終會為每種語言編寫基本相同的IL。 然而,結果更令人震驚: C#的代碼運行速度比使用相同操作的VB慢三倍!

生成的IL是不同的,但並非極端如此,而且我不太善於閱讀它以理解差異。

基准

我已經在下面列出了每個代碼。 在我的機器上,VB在大約6.36秒內找到了348513個素數。 C#在21.76秒內找到相同數量的素數。

計算機規格和注釋

  • 英特爾酷睿2四核6600 @ 2.4Ghz

我在那里測試的每台機器在C#和VB.NET之間的基准測試結果上都有明顯的差異。

兩個控制台應用程序都是在發布模式下編譯的,但是否則沒有從Visual Studio 2008生成的默認值更改項目設置。

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#代碼

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

    }
}

為什么C#的Math.Sqrt()執行速度比VB.NET慢?

C#實現每次循環都會重新計算Math.Sqrt(suspectPrime) ,而VB只在循環開始時計算它。 這僅僅是由於控制結構的性質。 在C#中, for只是一個奇特的while循環,而在VB中它是一個單獨的構造。

使用這個循環甚至會得分:

        Int32 sqrt = (int)Math.Sqrt(suspectPrime)
        for (Int32 i = 2; i <= sqrt; i++) { 
            if (suspectPrime % i == 0) 
                return; 
        }

我同意C#代碼在每次迭代時計算sqrt的說法,這里是Reflector的直接證明:

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

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

哪一種指向VB生成的代碼即使開發人員天真到足以在循環定義中調用sqrt也能表現得更好。

這是來自for循環的反編譯IL。 如果你比較兩者,你會看到VB.Net只執行Math.Sqrt(...) onces,而C#在每次傳遞時檢查它。 要解決這個問題,你需要做一些像var sqrt = (int)Math.Sqrt(suspectPrime); 正如其他人所說。

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

關閉切線,如果你已經啟動並運行VS2010,你可以利用PLINQ並更快地制作C#(可能也是VB.Net)。

將for for循環更改為......

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

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

我在機器上從7.4 - > 4.6秒開始。 將其移至發布模式可以節省更多時間。

不同之處在於循環; 你的C#代碼計算每次迭代的平方根。 改變這一行:

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

至:

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

把我的機器上的時間從26秒減少到7.something。

一般沒有。 它們都編譯為CLR(公共語言運行時)字節碼。 這類似於JVM(Java虛擬機)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM