类型比较的性能成本

时间:2012-01-20 09:21:48

标签: c# performance types

我正在从二进制流中解码通信消息。我根据已到达的消息创建不同类型的消息对象。它们都来自基类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是否有任何相似之处。

3 个答案:

答案 0 :(得分:8)

您是否考虑过删除类型广播?

我猜你已经考虑过将虚拟方法放在Message类型本身上会破坏分层抽象(例如,您可能希望将消息处理与消息本身完全分开)。也许考虑visitor pattern。这样您就可以将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 :(得分:2)

请注意,您的代码在语法上无效,因为返回类型为void,但无论如何。

嗯,我不太确定你展示的两种替代方案的性能差异。但是,至少FxCop会“suggest”而不是你的第一个解决方案:

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”类上提供“虚拟void 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,如果没有其他事可做的话。

答案 2 :(得分:1)

添加上面的优秀答案:

在性能分析中,我注意到使用is后跟as实际上导致性能低于单个as后跟空检查。不要指望编译器自动优化任何东西。您可以假设在消息传递代码(或其他性能关键部分)中,设计速度至关重要。

到目前为止,最快的演员阵容是静态演员阵容,其表现优于as,即var message = (SpecificType)baseMessage将胜过var message = baseMessage as SpecificType。这只是一个兴趣点,因为静态演员无法帮助您。

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

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

致以最诚挚的问候,