类型比较的性能成本

问题描述 投票:0回答:3

我正在从二进制流解码通信消息。我根据到达的消息创建不同类型的消息对象。它们都源自基本

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 是否有任何相似之处。

c# performance types
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(Message2 msg2); }

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

void ProcessIncomingMessage(CommsMessage msg) { new MyVisitor().Visit(msg); } class MyVisitor : CommsMessageVisitor { void Accept(Message1 msg1) { ProcessMessageType1(msg1); } void Accept(Message2 msg2) { ProcessMessageType2(msg2); } }

当然,可能有一些原因你不能这样做,但如果可以的话,避免类型转换总是更好!


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”类上提供一个“virtual 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

    
补充上面的优秀答案:


1
投票
is

后接

as

实际上会导致性能低于单个

as
后接空检查。不要指望编译器会自动优化任何东西。您的假设是正确的,在消息传递代码(或其他性能关键部分)中,速度设计至关重要。

到目前为止,最快的转换是静态转换,其性能优于
as
,即

var message = (SpecificType)baseMessage

将优于

var message = baseMessage as SpecificType
。这只是一个有趣的点,因为静态转换无法在您的情况下帮助您。

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

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


致以最诚挚的问候,

© www.soinside.com 2019 - 2024. All rights reserved.