Стоимость производительности сравнений типов - PullRequest
6 голосов
/ 20 января 2012

Я декодирую сообщения из двоичного потока. Я создаю объекты сообщений разных типов в зависимости от того, какие сообщения поступили. Все они происходят от базового типа 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 ]

8 голосов
/ 20 января 2012

Рассматривали ли вы исключение приведений типов?

Полагаю, вы полагали, что помещение виртуального метода в тип 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); }
}

Конечно, могут быть причины, по которым вы не можете этого сделать, но всегда лучше избегать приведения типов, если можете!

2 голосов
/ 20 января 2012

Обратите внимание, что ваш код синтаксически недействителен, так как типы возвращаемых значений 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
}

Конечно, здесь есть другие проблемы, такие как ремонтопригодность, понятность и т. Д. Возможно, этобыло бы лучше предоставить «virtual void ProcessMessage ()» в вашем классе «CommsMessage», который вы перезаписываете для каждого «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 голос
/ 20 января 2012

Чтобы добавить к отличным ответам выше:

В профилировании производительности я заметил, что использование is с последующим as на самом деле приводило к снижению производительности, чем один as с последующей нулевой проверкой. Не ожидайте, что компилятор автоматически что-то оптимизирует. Вы правы, если предположите, что в коде обмена сообщениями (или в других разделах, критичных к производительности) проектирование для скорости имеет первостепенное значение.

Самым быстрым броском является статическое приведение, которое превосходит as, т.е. var message = (SpecificType)baseMessage будет превосходить var message = baseMessage as SpecificType. Это интересный момент только потому, что статическое приведение не может помочь вам в вашем случае.

Поскольку в двух ответах уже упоминалось, что выполнение вышеупомянутого в полиморфной форме с использованием шаблона проектирования может быть лучшим решением, так как оно добавляет только вызов виртуального метода. Извлечение общего метода в абстрактный класс (или сигнатура общего метода для интерфейса) - безусловно, самое элегантное решение. Из-за накладных расходов на вызов виртуального метода это можно уменьшить, помечая конкретные методы на производных типах с помощью ключевого слова sealed.

Наконец, по возможности используйте обобщенные объекты для исключения приведений, поскольку обобщенные методы являются оптимизацией во время компиляции, а не приведения во время выполнения.

С уважением,

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...