类型比较的性能成本

6
我正在从二进制流中解码通信消息。根据到达的消息类型,我创建不同类型的消息对象。它们都派生自一个基础的CommsMessage类型。一切都很好。
在我的代码的其他地方,我需要对这些消息做出反应,因此我需要知道它是什么类型的消息。
目前我正在执行以下操作:
void ProcessIncomingMessage(CommsMessage msg)
{
    if (msg is MessageType1)
        return ProcessMessageType1(msg as MessageType1);

    if (msg is MessageType2)
        return ProcessMessageType2(msg as MessageType2);

    //etc
}

我想知道比较这些类型的性能成本,以及是否应该在基类中包含MessageType属性。那么我可以这样做:

void ProcessIncomingMessage(CommsMessage msg)
{
    switch (msg.MessageType)
    {
         case MessageType.Type1:  return ProcessMessageType1(msg as MessageType1);
         case MessageType.Type2:  return ProcessMessageType2(msg as MessageType2);

         //etc
    }
}

是的,这是过早的优化,我可能在担心微不足道的细节,但我是那种喜欢知道底层情况的程序员,所以想知道两者之间的性能差异。我猜我对类型比较有偏见,因为我的C++背景中RTTI引入了开销,只是想知道.Net是否有类似的情况。


1
可能是C# 'is'运算符性能的重复问题。 - George Duckett
3个回答

9

你是否考虑过消除类型转换?

我猜想你已经考虑过将虚方法放在Message类型本身上会破坏分层抽象(例如,你可能希望将消息的处理与消息本身分开)。也许可以考虑访问者模式。这将允许你将Message类与Message本身的处理分离出来。

如果你有这样的结构。

abstract class CommsMessage {}
class Message1 : CommsMessage {}
class Message2 : CommsMessage {}

你可以重构为

abstract class CommsMessage 
{ 
    public abstract void Visit(CommsMessageVisitor v);
}

class Message1 : CommsMessage 
{
    public void Visit(CommsMessageVisitor v) { v.Accept(this); }
}

class Message2 : CommsMessage 
{
    public void Visit(CommsMessageVisitor v) { v.Accept(this); }
}

interface CommsMessageVisitor 
{
   void Accept(Message1 msg1);
   void Accept(Message1 msg2);
}

此时,您已经消除了类型转换。现在,您可以将代码重写为

void ProcessIncomingMessage(CommsMessage msg) 
{
  new MyVisitor().Visit(msg);
}

class MyVisitor : CommsMessageVisitor
{
    void Accept(Message1 msg1) { ProcessMessageType1(msg1); }
    void Accept(Message1 msg2) { ProcessMessageType2(msg2); }
}

当然,你可能有无法避免的原因,但如果可以的话,避免类型转换总是更好的选择!

1
+1 对于您解释/假设为什么这里可能需要使用“vistor”的理解 - 否则似乎有些过头了;-) - Christian.K
我同意,除非这是情况所需,否则绝对过度!不过这似乎是一个相当常见的情况。你有一些简单的对象(POCO / 实体),只想将它们传输到不同的位置(可能在客户端/服务器之间共享,或者仅在不同的抽象级别)。你通常希望以不同的方式处理它,但将其塞入虚拟方法会暴露太多信息。访问者模式非常适合移出这些方法的实现。 - Jeff Foster
是的,考虑到所涉及的类型和方法的名称,你的假设是合理的。 - Christian.K
1
一个正确的假设。太棒了,谢谢!整个类型比较的事情让我感觉不对劲,但我找不到一个干净的方法来分离它们。我想是时候完成阅读《设计模式》了! - GazTheDestroyer

2
请注意,您的代码在语法上不合法,因为返回类型是“void”,但无论如何。
嗯,我不太确定您展示的两种替代方案的性能差异。然而,至少 FxCop 会建议使用以下内容而不是您的第一种解决方案:
void ProcessIncomingMessage(CommsMessage msg)
{
  MessageType1 msg1 = msg as MessageType1;

  if (msg1 != null)
  {
      ProcessMessageType1(msg1);
      return;
  }

  MessageType2 msg2 = msg as MessageType2;

  if (msg2 != null)
  {
      ProcessMessageType2(msg2);
      return;
  }


  //etc
}

当然,这里还涉及到其他问题,比如可维护性、可理解性等等。也许更好的做法是在你的“CommsMessage”类上提供一个“虚空 ProcessMessage()”方法,你可以为每个“MessageType”重写该方法,然后让CLR为你工作。
public class CommsMessage
{
    public virtual void ProcessMessage()
    {
       // Common stuff.
    }
}

public class MessageType1 : CommsMessage
{
   public override void ProcessMessage()
   {
      base.ProcessMessage();
      // type 1 specific stuff.
   }
}

// ...

void ProcessIncomingMessage(CommsMessage msg)
{
   msg.ProcessMessage();
}

可以说,如果没有其他任务需要处理,你可以直接调用 msg.ProcessMessage(),而不是现在调用的 ProcessIncomingMessage

1
жИСзїЩдљ†зВєиµЮпЉМasдЄОз©ЇеАЉж£АжЯ•жѓФisеЖНиЈЯзЭАasжЫійЂШжХИгАВ - Dr. Andrew Burnett-Thompson

1

补充以上优秀的答案:

在性能分析中,我注意到使用is后跟as实际上比单个as后跟空检查的性能要低。不要指望编译器会自动优化任何内容。你是正确的,假设在消息代码(或其他性能关键部分)中,为速度而设计至关重要。

迄今为止,最快的转换是静态转换,它优于as,即var message = (SpecificType)baseMessage将优于var message = baseMessage as SpecificType。这只是一个有趣的点,因为静态转换无法帮助您解决问题。

正如两个答案已经提到的,使用设计模式以多态方式执行上述操作可能是最佳解决方案,因为它只添加了一个虚拟方法调用。将公共方法提取到抽象类(或将公共方法签名提取到接口)是最优雅的解决方案。调用虚拟方法的开销可以通过使用sealed关键字标记派生类型上的特定方法来减轻。

最后,在可能的情况下使用泛型来消除转换,因为泛型方法是编译时优化,而不是运行时转换。

最好的祝福,


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接