【问题标题】:c# dot operator overhead: What is more efficientc#点运算符开销:什么更有效
【发布时间】:2017-08-26 06:01:31
【问题描述】:

所以我有一个 WPF 应用程序,它有一个带有子 MVVM 的基本 MVVM。我尝试用谷歌搜索答案,但不确定技术术语,所以我将在下面提供两个示例,也许有人可以让我对示例的效率有所了解。我想知道开销差异是否很小。

假设我有一个类似于以下的设置

public class ParentViewModel
{
    public ParentViewModel()
    {
        Child = new ChildViewModel();
    }

    public ChildViewModel Child { get; set; }
}

public class ChildViewModel
{
    public ChildViewModel()
    {
        GrandChild = new GrandChildViewModel();
    }

    public GrandChildViewModel GrandChild { get; set; }
}

public class GrandChildViewModel
{
    public GrandChildViewModel()
    {
        GreatGrandChild = new GreatGrandChildViewModel();
    }

    public GreatGrandChildViewModel GreatGrandChild { get; set; }
}

public class GreatGrandChildViewModel
{
    public GreatGrandChildViewModel()
    {
         intA = 1;
         intB = 2;
         intC = 3;
    }

    public int intA { get; set; }
    public int intB { get; set; }
    public int intC { get; set; }
}

以下两个使用示例是我想要了解的地方。

示例 1:

public Main()
{
     var parent = new ParentViewModel();

     Console.WriteLine($"A: {parent.Child.GrandChild.GreatGrandChild.intA}" +
                       $"B: {parent.Child.GrandChild.GreatGrandChild.intB}" +
                       $"C: {parent.Child.GrandChild.GreatGrandChild.intC}");
}

示例 2:

public Main()
{
     var greatGrandChild = new ParentViewModel().Child.GrandChild.GreatGrandChild;

     Console.WriteLine($"A: {greatGrandChild.intA}" +
                       $"B: {greatGrandChild.intB}" +
                       $"C: {greatGrandChild.intC}");
}

哪个效率更高?我之所以问是因为我认为示例 2 会更有效,因为它会下降到最低级别一次,然后访问 intA、intB 和 intC。 这有关系吗?性能差异是否显着?

【问题讨论】:

  • 几乎肯定不足以覆盖任何其他因素,例如可读性或灵活性。
  • a) 在 WPF 应用程序中,这几乎肯定不会很重要。任何潜在的差异都可能是处理量的千分之一与写入流或执行字符串操作的行为相比,以及刷新 UI 成本的百万分之一。 b) 如果这些在您构建发布时有效地生成相同的 IL 代码,我不会感到特别惊讶。
  • 您正在编写 WPF 应用程序,而不是一些低级硬件驱动程序。 WPF 所做的任何事情都会比这花费更多的时间。但是,如果属性不是简单的{get;set;},而是在 getter 中发生了一些重要的事情 - 那么第二种方式会更好。
  • 如果你想知道有什么区别,请测量它。你有两个程序。
  • 循环 1000 万次(不写入控制台),示例 1 大约是 5.5 秒,示例 2 在我的机器上大约是 5.1 秒。我很好奇

标签: c# dot-operator


【解决方案1】:

您会注意到两者之间绝对没有优化。事实上,我怀疑编译器会将这两种类型的语句优化为同一个 IL。

但是,后一个示例更具可读性,因此我会选择这种方法。

【讨论】:

  • 这就是我要走的路线,看起来它也是更有效的路线(虽然它可能可以忽略不计)。谢谢。
【解决方案2】:

我建议您购买所需的最小对象。

在您给出的示例中,性能差异可以忽略不计,但如果父/祖父/曾祖父对象中恰好有更多数据,并且如果您正在传递此对象(尤其是通过网络),那么它可以有所作为。想象一下,将某人的整个家谱对象传递给某个真正只需要此人姓名的 Web 服务。

但它也通过抓取您需要的最小物体来显示您的意图。故意编程通常更易于阅读和维护,并且可以让您更轻松地发现错误。

【讨论】:

    【解决方案3】:

    虽然最初的想法是编译器会针对相同的 IL 进行优化,但显然不是这样

    虽然我没有检查 IL,但快速而肮脏的秒表测试表明,孙子路线要快得多

    使用问题中的视图模型,结果如下:

    var parent = new ParentViewModel();
    var greatGrandChild = new ParentViewModel().Child.GrandChild.GreatGrandChild;
    var watch = new Stopwatch();
    
    var a = parent.Child.GrandChild.GreatGrandChild.intA;
    var b = parent.Child.GrandChild.GreatGrandChild.intB;
    var c = parent.Child.GrandChild.GreatGrandChild.intC;
    
    var bothTotal = 0L;
    var longTotal = 0L;
    var shortTotal = 0L;
    
    watch.Start();
    a = parent.Child.GrandChild.GreatGrandChild.intA;
    b = parent.Child.GrandChild.GreatGrandChild.intB;
    c = parent.Child.GrandChild.GreatGrandChild.intC;
    a = greatGrandChild.intA;
    b = greatGrandChild.intB;
    c = greatGrandChild.intC;
    watch.Stop();
    bothTotal += watch.ElapsedTicks;
    watch.Reset();          
    Console.WriteLine("Longhand and Shorthand: " + bothTotal);
    
    watch.Start();
    a = parent.Child.GrandChild.GreatGrandChild.intA;
    b = parent.Child.GrandChild.GreatGrandChild.intB;
    c = parent.Child.GrandChild.GreatGrandChild.intC;
    a = parent.Child.GrandChild.GreatGrandChild.intA;
    b = parent.Child.GrandChild.GreatGrandChild.intB;
    c = parent.Child.GrandChild.GreatGrandChild.intC;
    watch.Stop();
    longTotal += watch.ElapsedTicks;
    watch.Reset();          
    Console.WriteLine("Longhand Only: " + longTotal);
    
    watch.Start();
    a = greatGrandChild.intA;
    b = greatGrandChild.intB;
    c = greatGrandChild.intC;
    a = greatGrandChild.intA;
    b = greatGrandChild.intB;
    c = greatGrandChild.intC;
    watch.Stop();
    shortTotal += watch.ElapsedTicks;
    watch.Reset();          
    Console.WriteLine("Shorthand Only: " + shortTotal);
    

    这些是我的典型结果:

    Longhand and Shorthand: 22
    Longhand Only: 3
    Shorthand Only: 2
    

    这显然不是最精细的测试,而且肯定有一些关于它的事情可以被认为倾向于确认偏差 - 但从表面上看,“速记”路线似乎更理想,值得进一步测试

    更新

    我查看并检查了生成的 IL,结果非常清楚:

    速写:

    // Setup the object:
    newobj     instance void ParentViewModel::.ctor()
    
    // Actually call the members:
    callvirt   instance class ChildViewModel ParentViewModel::get_Child()
    callvirt   instance class GrandChildViewModel ChildViewModel::get_GrandChild()
    callvirt   instance class GreatGrandChildViewModel 
    GrandChildViewModel::get_GreatGrandChild()
    callvirt   instance int32 GreatGrandChildViewModel::get_intA()
    callvirt   instance class ChildViewModel ParentViewModel::get_Child()
    callvirt   instance class GrandChildViewModel ChildViewModel::get_GrandChild()
    callvirt   instance class GreatGrandChildViewModel 
    GrandChildViewModel::get_GreatGrandChild()
    callvirt   instance int32 GreatGrandChildViewModel::get_intB()
    callvirt   instance class ChildViewModel ParentViewModel::get_Child()
    callvirt   instance class GrandChildViewModel ChildViewModel::get_GrandChild()
    callvirt   instance class GreatGrandChildViewModel 
    GrandChildViewModel::get_GreatGrandChild()
    callvirt   instance int32 GreatGrandChildViewModel::get_intC()
    

    速记:

    // Setup the object
    newobj     instance void ParentViewModel::.ctor()
    call       instance class ChildViewModel ParentViewModel::get_Child()
    callvirt   instance class GrandChildViewModel ChildViewModel::get_GrandChild()
    callvirt   instance class GreatGrandChildViewModel GrandChildViewModel::get_GreatGrandChild()
    
    // Actually call the members:
    callvirt   instance int32 GreatGrandChildViewModel::get_intA()
    callvirt   instance int32 GreatGrandChildViewModel::get_intB()
    callvirt   instance int32 GreatGrandChildViewModel::get_intC()
    

    【讨论】:

    • 谢谢,我不确定如何进行性能测量。如果您对在哪里寻找更全面的性能测试有任何建议,我们将不胜感激。
    • @Chris 真正获得正确明确答案的最佳方法是构建一个简单的项目并在ildasm 中打开它以查看编译器生成的代码是什么......然后比较两种技术来查看编译器对哪一种优化更多
    • @Chris 我经历过并有时间检查 IL 并用结果更新了我的答案
    猜你喜欢
    • 1970-01-01
    • 2013-11-26
    • 1970-01-01
    • 2020-11-02
    • 1970-01-01
    • 2010-10-24
    • 2010-11-17
    • 2011-02-23
    • 1970-01-01
    相关资源
    最近更新 更多