[英]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秒內找到相同數量的素數。
我在那里測試的每台機器在C#和VB.NET之間的基准測試結果上都有明顯的差異。
兩個控制台應用程序都是在發布模式下編譯的,但是否則沒有從Visual Studio 2008生成的默認值更改項目設置。
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
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.