我想知道是否有人可以向我解释一下编译器可能正在为我做些什么来观察一个简单方法的性能差异。
public static uint CalculateCheckSum(string str) {
char[] charArray = str.ToCharArray();
uint checkSum = 0;
foreach (char c in charArray) {
checkSum += c;
}
return checkSum % 256;
}
我正在与一位同事一起为消息处理应用程序做一些基准测试/优化。在Visual Studio 2012中使用相同的输入字符串执行此函数的1000万次迭代大约需要25秒,但是当使用“优化代码”选项构建项目时,打开相同的代码,在7秒内执行相同的1000万次迭代。
我非常有兴趣了解编译器在幕后做了什么,以便我们能够看到一个看似无辜的代码块,例如这个性能提升超过3倍。
根据要求,这是一个完整的控制台应用程序,用于演示我所看到的内容。
class Program
{
public static uint CalculateCheckSum(string str)
{
char[] charArray = str.ToCharArray();
uint checkSum = 0;
foreach (char c in charArray)
{
checkSum += c;
}
return checkSum % 256;
}
static void Main(string[] args)
{
string stringToCount = "8=FIX.4.29=15135=D49=SFS56=TOMW34=11752=20101201-03:03:03.2321=DEMO=DG00121=155=IBM54=138=10040=160=20101201-03:03:03.23244=10.059=0100=ARCA10=246";
Stopwatch stopwatch = Stopwatch.StartNew();
for (int i = 0; i < 10000000; i++)
{
CalculateCheckSum(stringToCount);
}
stopwatch.Stop();
Console.WriteLine(stopwatch.Elapsed);
}
}
在调试中运行优化关闭我看到13秒,我得到2秒。
在Release中运行优化时间为3.1秒和2.3秒。
答案 0 :(得分:7)
要查看 C#编译器为您做什么,您需要查看IL。如果你想看看它如何影响JITted代码,你需要查看Scott Chamberlain所描述的本机代码。请注意,JITted代码将根据处理器体系结构,CLR版本,流程的启动方式以及可能的其他内容而有所不同。
我通常从IL开始,然后可能查看JITted代码。
使用ildasm
比较IL可能有点棘手,因为它包含每条指令的标签。以下是使用和不使用优化(使用C#5编译器)编译的方法的两个版本,删除了无关标签(和nop
指令)以使它们尽可能易于比较:
<强>优化强>
.method public hidebysig static uint32
CalculateCheckSum(string str) cil managed
{
// Code size 46 (0x2e)
.maxstack 2
.locals init (char[] V_0,
uint32 V_1,
char V_2,
char[] V_3,
int32 V_4)
ldarg.0
callvirt instance char[] [mscorlib]System.String::ToCharArray()
stloc.0
ldc.i4.0
stloc.1
ldloc.0
stloc.3
ldc.i4.0
stloc.s V_4
br.s loopcheck
loopstart:
ldloc.3
ldloc.s V_4
ldelem.u2
stloc.2
ldloc.1
ldloc.2
add
stloc.1
ldloc.s V_4
ldc.i4.1
add
stloc.s V_4
loopcheck:
ldloc.s V_4
ldloc.3
ldlen
conv.i4
blt.s loopstart
ldloc.1
ldc.i4 0x100
rem.un
ret
} // end of method Program::CalculateCheckSum
<强>未优化强>
.method public hidebysig static uint32
CalculateCheckSum(string str) cil managed
{
// Code size 63 (0x3f)
.maxstack 2
.locals init (char[] V_0,
uint32 V_1,
char V_2,
uint32 V_3,
char[] V_4,
int32 V_5,
bool V_6)
ldarg.0
callvirt instance char[] [mscorlib]System.String::ToCharArray()
stloc.0
ldc.i4.0
stloc.1
ldloc.0
stloc.s V_4
ldc.i4.0
stloc.s V_5
br.s loopcheck
loopstart:
ldloc.s V_4
ldloc.s V_5
ldelem.u2
stloc.2
ldloc.1
ldloc.2
add
stloc.1
ldloc.s V_5
ldc.i4.1
add
stloc.s V_5
loopcheck:
ldloc.s V_5
ldloc.s V_4
ldlen
conv.i4
clt
stloc.s V_6
ldloc.s V_6
brtrue.s loopstart
ldloc.1
ldc.i4 0x100
rem.un
stloc.3
br.s methodend
methodend:
ldloc.3
ret
}
注意事项:
blt.s
而不是clt
后跟brtrue.s
(这是其中一个额外本地人的原因)。 答案 1 :(得分:6)
为了更好地理解,您应该查看生成的IL代码。
编译程序集,然后复制它并使用优化再次编译。然后打开.net反射器中的两个程序集,并比较编译的IL的差异。
更新: Dotnet Reflector可在http://www.red-gate.com/products/dotnet-development/reflector/
获得更新2: IlSpy似乎是一个很好的开源替代品。 http://ilspy.net/
答案 2 :(得分:5)