我进行了一项测试,测量下面两个访问器功能之间的速度差异,时间差异大于我的预期。我只是觉得速记实现可能会快一点,所以我想测试一下。
我测量了所需的总秒数,为每个类调用Get函数,进行10亿次迭代。
using System;
using System.Diagnostics;
class SimpleGet {
int value;
public int Get() {
return value;
}
}
class ShorthandGet {
int value;
public int Get() => value;
}
class Program {
static void Main() {
const int Iterations = 1000000000;
Stopwatch sw = new Stopwatch();
sw.Start();
{
int n; SimpleGet sg = new SimpleGet();
for (int i = 0; i < Iterations; i++) {
n = sg.Get();
}
}
sw.Stop();
Console.WriteLine("SimpleGet: " + sw.Elapsed.TotalSeconds);
sw.Reset();
sw.Start();
{
int n; ShorthandGet shg = new ShorthandGet();
for (int i = 0; i < Iterations; i++) {
n = shg.Get();
}
}
sw.Stop();
Console.WriteLine("ShorthandGet: " + sw.Elapsed.TotalSeconds);
Console.ReadLine();
}
}
结果:
// 1 billion iterations
SimpleGet: 11.8484244
ShorthandGet: 4.3218568
速度的差异是巨大的。我能看到的唯一区别是常规函数有括号,因此在函数调用中创建一个新的作用域。由于范围内没有新变量,理论上不应该忽视&#34;忽视&#34;有人可以解释为什么常规功能没有被优化到与另一个相同的水平吗?
修改
我使用属性测试了相同的方案:Value { get { return value; } }
和Value => value;
,时间差异非常接近各自的功能时差。我认为原因是一样的。
答案 0 :(得分:8)
简短的回答是,正确完成基准测试没有区别。
对于像这样的微优化案例,我总是先看看IL。不是因为你会得到一些深刻的见解,而是因为如果生成相同的IL,那么在运行时应该没有差别。接下来要记住的是,您必须从Release版本开始,因为编译器将删除这些版本中不必要的IL指令。
在Debug构建中,长格式IL(SimpleGet)有额外的指令来启用断点:
.method public hidebysig
instance int32 Get () cil managed
{
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld int32 ConsoleApplication7.SimpleGet::'value'
IL_0007: stloc.0
IL_0008: br.s IL_000a
IL_000a: ldloc.0
IL_000b: ret
}
与ShorthandGet相比要短得多:
.method public hidebysig
instance int32 Get () cil managed
{
IL_0000: ldarg.0
IL_0001: ldfld int32 ConsoleApplication7.ShorthandGet::'value'
IL_0006: ret
}
然而,在优化构建中,两种形式都会产生与上述ShorthandGet相同的IL。
Debug版本的基准测试可能会显示您所演示的差异,但这些内容永远不值得比较,因为如果您关心性能,那么您将运行Release版本的优化代码。故事的寓意是始终对优化的代码进行性能分析。经常遗漏的另一个项目是在没有附加调试器的情况下进行基准测试,因为即使对于优化的IL,JIT也会检测调试器并发出更多可调试的机器代码。许多人都错过了这个,因为他们只需点击“开始”或在VS中点击F5,但是这个启动附带调试器的程序。使用菜单选项Debug&gt;启动时不进行调试。