Jes*_*ter 15 c# compiler-construction visual-studio
我想知道是否有人可以向我解释一下编译器可能正在为我做些什么来观察一个简单方法的性能差异.
public static uint CalculateCheckSum(string str) {
char[] charArray = str.ToCharArray();
uint checkSum = 0;
foreach (char c in charArray) {
checkSum += c;
}
return checkSum % 256;
}
Run Code Online (Sandbox Code Playgroud)
我正在与一位同事一起为消息处理应用程序做一些基准测试/优化.在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);
}
}
Run Code Online (Sandbox Code Playgroud)
在调试中运行优化关闭我看到13秒,我得到2秒.
在发布中运行,优化时间为3.1秒和2.3秒.
要查看C#编译器为您执行的操作,您需要查看IL.如果你想看看它如何影响JITted代码,你需要查看Scott Chamberlain所描述的本机代码.请注意,JITted代码将根据处理器体系结构,CLR版本,进程的启动方式以及可能的其他内容而有所不同.
我通常会从IL开始,然后可能会查看JITted代码.
比较IL使用ildasm可能有点棘手,因为它包含每个指令的标签.以下是使用和不使用优化(使用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
Run Code Online (Sandbox Code Playgroud)
未优化
.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
}
Run Code Online (Sandbox Code Playgroud)
注意事项:
blt.s而不是clt后跟brtrue.s(这是其中一个额外本地人的原因).为了更好地理解,您应该查看生成的IL代码.
编译程序集,然后复制它并使用优化再次编译.然后打开.net反射器中的两个组件并比较编译的IL的差异.
更新:Dotnet Reflector可在http://www.red-gate.com/products/dotnet-development/reflector/上找到
更新2:IlSpy似乎是一个很好的开源替代品. http://ilspy.net/
我不知道它在做什么优化,但是我可以向您展示如何找到自己的优化方法。
首先构建经过优化的代码,然后在不附加调试器的情况下启动它(如果附加了调试器,JIT编译器将生成不同的代码)。运行您的代码,以便您至少知道一次输入该部分,以便JIT编译器有机会对其进行处理,然后在Visual Studio中转到Debug->Attach To Process...。从新菜单中选择正在运行的应用程序。
在您想知道的地方放置一个断点,让程序停止,一旦停止,转至Debug->Windows->Dissasembly。这将向您显示JIT创建的已编译代码,并且您将能够检查它在做什么。
| 归档时间: |
|
| 查看次数: |
5505 次 |
| 最近记录: |