背景
今天早上,在运行基准测试时,我和我的同事发现了一些关于C#代码与VB.NET代码性能的奇怪事情。
我们开始比较C#和Delphi Prism计算质数,发现Prism大约快了30%。我认为CodeGear在生成IL时对代码进行了更多的优化( exe
大约是C#的两倍,并且包含各种不同的IL )。
我决定也用VB.NET编写一个测试,假设微软的编译器最终会为每种语言编写基本上相同的IL。然而,结果更令人震惊:使用相同的操作,代码在C#上的运行速度比VB慢三倍以上!
生成的IL是不同的,但不是非常不同,我在阅读它方面不够好,无法理解其中的差异。
基准测试
我已经包含了下面每一个的代码。在我的机器上,VB在大约6.36秒内找到了348513个素数。C#在21.76秒内找到相同数量的素数。
计算机规格和说明
在我测试过的每一台机器上,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慢?
发布于 2010-06-12 04:28:16
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;
}
发布于 2010-06-12 04:34:13
我同意这样的说法,即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,代码也会执行得更好。
发布于 2010-06-12 04:50:24
下面是从for循环反编译的IL。如果将两者进行比较,您会发现VB.Net只执行一次Math.Sqrt(...)
,而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
https://stackoverflow.com/questions/3025968
复制相似问题