c# - 为什么速记访问器函数比它们的常规访问器函数更快?

标签 c# clr

我运行了一个测试来测量下面两个访问器函数之间的速度差异,时间差异比我预期的要大。我只是觉得简写实现可能会快一点,所以我想测试一下。

我测量了为每个类调用 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

速度差别很大。我能看到的唯一区别是常规函数有括号,因此在函数调用时创建了一个新的范围。由于作用域内没有新变量,理论上它不应该被“忽略”吗?有人可以解释为什么常规函数没有被优化到与另一个相同的水平吗?

编辑

我用属性测试了相同的场景:Value { get { return value; } } Value => value; 并且我们的时间差非常接近各自函数的时间差。我认为原因是一样的。

最佳答案

简短的回答是正确完成基准测试没有区别。

对于像这样的微优化案例,我总是喜欢先看一下 IL。不是因为您会获得一些深刻的见解,而是因为如果生成相同的 IL,那么在运行时应该没有区别。接下来要记住的是,您必须从发布版本开始,因为编译器会删除这些版本中不必要的 IL 指令。

在调试版本中,长格式 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。

调试版本的基准测试可能会像您所展示的那样显示差异,但这些永远不值得比较,因为如果您关心性能,您将运行发布版本的优化代码。这个故事的寓意是始终对优化代码进行性能分析。另一个经常被忽略的项目是在没有附加调试器的情况下进行基准测试,因为即使对于优化的 IL,JIT 也会检测调试器并发出更多可调试的机器代码。许多人都错过了这一点,因为他们只是在 VS 中单击“开始”或按 F5,但这启动程序时附加了调试器。使用菜单选项 Debug > Start Without Debugging。

关于c# - 为什么速记访问器函数比它们的常规访问器函数更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37557259/

相关文章:

c# - IList<T>.FindIndex(Int32, Predicate <T>)

c# - x86 CPU 执行哪些类型的重新排序优化?

.net - 在 C++ .net4 中使用和不使用调试器的不同代码行为

c# - 如果永远不会使用引用,持有对对象的引用是否会将其保留在内存中?

c# - C# 中的 `using a = Func<>;` 语法是什么?

c# - DropDownList 未在 ASP.NET MVC 中填充正确的值

c# - 将集合转换为字符串的最佳方法

c# - 如何在 dotnet pack nuget 包中包含来自构建输出 (dacpac) 的非 dll 文件?

.net - 自动将 CLR 数据库项目部署到数据库

c# - 是否在垃圾回收期间调用了所有终结器?