什么';s调用空Action对性能的影响

本文关键字:Action 性能 影响 调用 什么 | 更新日期: 2023-09-27 18:20:06

假设我们有一个这样的类:

public class Task
{
      public Action PostAction=()=>{}
      public void Do()
      {
             //Do Some Stuffs
             PostAction();
      }
}

正如你所看到的,PostAction在那里,所以我们可以注入一个PostAction。

现在假设我们有一个任务列表,其中一些任务有后期操作,而大多数没有,我们这样称呼它们:

foreach(var task in tasks)
      task.Do();

空的post操作对上述代码的执行有任何性能影响吗?

什么';s调用空Action对性能的影响

这种情况经常出现,因为您的代码在引发事件时也需要进行null测试。抖动优化器不能做任何事情来优化无所事事的调用,它不能超越委托调用。此代码的快速版本避免了进行代理调用:

public class Task {
      public Action PostAction;
      public void Do() {
          if (PostAction != null) PostAction();
      }
}

使用System.Diagnostics.Stopwatch分析差异非常棘手,委托调用非常快。我通过给Do()调用赋予[MethodImpl(MethodImplOptions.Noinling)]属性来确保它不能内联。使用移动Haswell芯片在我速度不太快的笔记本电脑上的平均测量值:

  • 空Do():3.9纳秒/调用
  • 零测试:4.3纳秒/调用=4.3-3.9 ~=0.4纳秒
  • 调用:7.5纳秒/调用=7.5-3.9 ~=3.6纳秒

注意,"Empty Do()"度量包括执行测试、for(;)循环以及进行方法调用的开销。在所有的测量中,这都是恒定的开销。

可以看出成本,委托调用比空测试慢(3.6-0.4)/0.4 ~=800%。如果你假设恒定开销是有代表性的(事实并非如此),你可以得到更友好的数字:(7.5-4.3)/4.3~=75%慢。foreach循环将有更多的开销,从而降低性能损失百分比。使用Enumerable.Range和foreach迭代的测试产生(17.0-13.9)/13.9~=22%的速度减慢。

当然,这些数字相当可观。然而,绝对数非常低。如果你不经常调用委托,或者有很多//Do Some Stuffs(从而增加了恒定的开销),那么性能损失可能很难被注意到。

推荐的方法是测试null,如任何演示如何引发事件的示例代码所示。只有当您对委托将被替换(即事件将被订阅)抱有很高的期望时,才有充分的理由使用lambda。在这种情况下,您优化了null检查,每次调用节省了大约一个CPU周期。