【问题标题】:Is there any overhead in massively using .? operators for field/property comparisons?大量使用 . 是否有任何开销?字段/属性比较的运算符?
【发布时间】:2020-06-23 07:47:57
【问题描述】:

如果我们有 2 个对象,并且我们重载了一个相等运算符来比较每个对象,我们是否应该厌倦使用 ?运算符(例如 firstObject?.MyProperty == secondObject?.MyProperty)如果我们最终在 20-30 个属性上使用它?

基本上对于有很多属性的大对象,是不是做下面的比较比较好

(firstObject == null && secondObject == null || !(firstObject == null ^ secondObject == null)) 
&&  (<property comparisons without ?>)

或者干脆

&lt;property comparisons with ?&gt;

谢谢。

【问题讨论】:

  • 当您将其加载到执行十亿次相等检查的循环中时,性能测试的结果是什么?
  • 如果字段很多,先检查整个对象的null比较干净,避免重复代码
  • 没有比这更好的了,因为你没有给出可以更好的领域。空条件会产生更多的 IL,但看起来更整洁。然而,两者都做同样的事情。综上所述,性能上的任何差异都可以被优化掉
  • 我想这归结为一个问题“生产系统完成十亿次合法比较之前需要多少小时/天/年,节省的 9 秒锣值不值得?它与代码的清洁度相比……”。如果 prod 做 10 亿比较一个小时,它可能代表一个合理的节省;如果在比较计数被击中之前它已经运行了一年,那么它不值得丑陋(恕我直言)
  • @TheGeneral Null-conditional (aka "Elvis operator") 生成 less IL。

标签: c# .net performance .net-core operator-overloading


【解决方案1】:

在引擎盖下,它们不同?. 更具可读性和更快(似乎)。

证明

首先,考虑这个示例(定位.NET Framework 4.7.2):

课程 { Foo 类 { 公共 Foo 酒吧 { 得到;放; } } 静态无效主要(字符串 [] 参数) { var foo = new Foo(); var t0 = DateTime.UtcNow.Ticks; for (int i = 0; i

Release 模式下为Any CPU 平台编译它。现在让我们使用ildasm反编译生成的.exe

猫王操作员说明

.method private hidebysig static void  UsingElvisOperator(class OperatorTest.Program/Foo foo) cil managed
{
  // Code size       53 (0x35)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  brfalse.s  IL_0034
  IL_0003:  ldarg.0
  IL_0004:  callvirt   instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
  IL_0009:  brfalse.s  IL_0034
  IL_000b:  ldarg.0
  IL_000c:  callvirt   instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
  IL_0011:  callvirt   instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
  IL_0016:  brfalse.s  IL_0034
  IL_0018:  ldarg.0
  IL_0019:  callvirt   instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
  IL_001e:  callvirt   instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
  IL_0023:  callvirt   instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
  IL_0028:  brfalse.s  IL_0034
  IL_002a:  ldstr      "OK"
  IL_002f:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0034:  ret
} // end of method Program::UsingElvisOperator

以及if-checks的说明:

.method private hidebysig static void  UsingIfsAndButs(class OperatorTest.Program/Foo foo) cil managed
{
  // Code size       49 (0x31)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  brtrue.s   IL_0006
  IL_0003:  ldnull
  IL_0004:  br.s       IL_0024
  IL_0006:  ldarg.0
  IL_0007:  call       instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
  IL_000c:  dup
  IL_000d:  brtrue.s   IL_0013
  IL_000f:  pop
  IL_0010:  ldnull
  IL_0011:  br.s       IL_0024
  IL_0013:  call       instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
  IL_0018:  dup
  IL_0019:  brtrue.s   IL_001f
  IL_001b:  pop
  IL_001c:  ldnull
  IL_001d:  br.s       IL_0024
  IL_001f:  call       instance class OperatorTest.Program/Foo OperatorTest.Program/Foo::get_Bar()
  IL_0024:  brfalse.s  IL_0030
  IL_0026:  ldstr      "OK"
  IL_002b:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0030:  ret
} // end of method Program::UsingIfsAndButs

显然,生成的指令是不同的。

性能对比

在我的机器上运行这两种方法 10 亿次 次,得到以下结果:

UsingElvisOperator:   1704 ms
UsingIfsAndButs:      2815 ms

【讨论】:

  • 重要的是要注意,您实际上并没有对操作问题进行基准测试。哎呀。无论如何,加一个努力,我总是自己使用 null 条件
  • @TheGeneral 你是对的。对OP不是很了解,所以做了一些创造性的解读。
【解决方案2】:

只想分享我的基准测试总结,它检查 null 和非 null 测试对象,这显示了“ifs”在速度方面的优势:

// * Summary *
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.900 (1909/November2018Update/19H2)
Intel Core i5-7300U CPU 2.60GHz (Kaby Lake), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=3.1.301

  [Host] : .NET Core 3.1.5 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.27001), X64 RyuJIT

Job=NewJob  IterationCount=2  LaunchCount=2  
RunStrategy=Throughput  WarmupCount=1  

|             Method |     Mean |     Error |    StdDev |
|
------------------- |---------:|----------:|----------:|
|    UsingIfsAndButs | 1.518 ns | 0.4293 ns | 0.0664 ns |
| UsingElvisOperator | 2.144 ns | 0.2124 ns | 0.0329 ns |

和代码:

[SimpleJob(RunStrategy.Throughput, launchCount: 2, warmupCount: 1, targetCount: 2, id: "NewJob")]
public class ElvisBenchmarks
{
    Foo foo;
    Foo foo_null;
    Boolean isNull;
    
    public ElvisBenchmarks()
    {
         foo = new Foo();
    }

    [Benchmark]
    public void UsingIfsAndButs()
    {
        isNull = (foo != null && foo.Bar != null && foo.Bar.Bar != null && foo.Bar.Bar.Bar != null)
                ? true : false;

        isNull = (foo_null != null && foo_null.Bar != null && foo_null.Bar.Bar != null && foo_null.Bar.Bar.Bar != null)
                ? true : false;
    }
        
    [Benchmark]
    public void UsingElvisOperator()
    {
        isNull = (foo?.Bar?.Bar?.Bar != null) ? true : false;
        isNull = (foo_null?.Bar?.Bar?.Bar != null) ? true : false;
    }
}

class Foo
{
    public Foo Bar { get; set; }
}

【讨论】:

  • 有趣...关于为什么会发生这种情况的任何理论?如果你测试的不是嵌套的 Foo 而是 foo 的属性,我认为结果会适得其反。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-01-18
  • 2016-01-05
  • 2017-03-28
  • 1970-01-01
  • 2011-01-16
  • 2012-10-05
相关资源
最近更新 更多