2012-01-20 16 views
6

Dekoduje wiadomości komunikaty ze strumienia binarnego. Tworzę obiekty wiadomości o różnych typach w zależności od nadejścia wiadomości. Wszystkie pochodzą od podstawowego typu CommsMessage. Wszystko w porządku i dandy.Koszt wydajności porównań typów

Gdzie indziej w moim kodzie muszę reagować na te wiadomości, więc muszę wiedzieć, jaki typ wiadomości jest.

Obecnie robie:

void ProcessIncomingMessage(CommsMessage msg) 
{ 
    if (msg is MessageType1) 
     return ProcessMessageType1(msg as MessageType1); 

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

    //etc 
} 

Zastanawiam się co koszt wydajność porównywania tych typów są i czy powinienem to właściwość w klasie bazowej MessageType zamiast. Wtedy mógłbym zrobić:

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

     //etc 
    } 
} 

Tak, to jest przedwczesna optymalizacja, a ja chyba martwić się nieistotnych szczegółów, ale jestem typem kodera, który lubi wiedzieć, co się dzieje pod kołdrą i tak było zastanawia się różnice w wydajności między nimi. Chyba mam uprzedzenia przeciwko porównaniom typów z mojego tła C++, gdzie RTTI wprowadził obciążenie i zastanawiałem się, czy .Net ma jakiekolwiek podobieństwa.

+1

możliwe duplikat [C# 'jest' wydajność operatora] (http://stackoverflow.com/questions/686412/c-sharp-is-operator-performance) –

Odpowiedz

7

Czy zastanawiasz się nad wyeliminowaniem rzutów typu?

Zgaduję, że wziąłeś pod uwagę, że umieszczenie wirtualnej metody na samym typie Message złamie abstrakcję warstwowania (np. Możesz chcieć czystego oddzielenia przetwarzania wiadomości od samej wiadomości). Może rozważyć visitor pattern. Umożliwi to oddzielenie klasy Message od samego przetwarzania Message.

Jeśli masz coś z tej struktury.

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

Można byłaby do

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); 
} 

W tym momencie już wyeliminował typu odlewów. Teraz można przepisać kod jako

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

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

Oczywiście nie może być powodów nie może tego zrobić, ale to jest zawsze ładniejszy uniknąć typ rzuca jeśli potrafisz!

+1

+1 za wyjaśnienie/założenie, dlaczego vistor może tu być potrzebny - w przeciwnym razie wydawałoby się to przesadą ;-) –

+0

Zgadzam się, zdecydowanie przesadzę, chyba że tak jest! Wydaje się jednak, że jest to dość powszechny przypadek. Masz jakieś głupie przedmioty (POCO/entity), które chcesz przenieść do miejsc (może być dzielone między klientem/serwerem lub tylko na różnych poziomach abstrakcji). Często chcesz go przerobić, ale wrzucanie go w wirtualne metody obnaża za dużo. Wzorce odwiedzin są niesamowite, jeśli chodzi o wyprowadzanie implementacji tych metod. –

+0

Yup, i biorąc pod uwagę nazwy rodzajów i metod zaangażowanych, twoje jest rozsądnym założeniem. –

2

Należy zauważyć, że kod nie jest poprawny pod względem składni, ponieważ typy zwracane to void, ale tak czy inaczej.

Cóż, nie jestem pewien co do różnicy w wydajności obu pokazanych alternatyw. Jednak przynajmniej FxCop będzie „suggest” zamiast następujące swojego pierwszego rozwiązania:

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 
} 

Oczywiście, istnieją inne kwestie związane tutaj jak konserwacji, zrozumiałości itp Być może byłoby lepiej, tworząc " virtual void ProcessMessage() "w twojej klasie" CommsMessage ", którą zastępujesz dla każdego" MessageType ". Następnie pozwól, aby CLR działał dla Ciebie.

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(); 
} 

Prawdopodobnie można nazwać msg.ProcessMessage() bezpośrednio, gdzie obecnie nazywamy ProcessIncomingMessage, jeśli nie ma nic innego tam robić.

+1

+1 ode mnie - "as", po którym następuje zerowa kontrola, przewyższa "jest", a następnie "jak" –

1

dodać do doskonałych odpowiedzi powyżej:

W profilowania wydajności Zauważyłem, że przy użyciu is następnie as rzeczywiście spowodowało niższą wydajność niż pojedynczy as następnie zerowej czeku. Nie oczekuj, że kompilator automatycznie zoptymalizuje cokolwiek. Masz rację, zakładając, że w kodzie wiadomości (lub w inny sposób krytycznych pod względem wydajności), że projektowanie dla prędkości ma ogromne znaczenie.

Zdecydowanie najszybszy rzut to rzut statyczny, który przewyższa wartość as, tj. var message = (SpecificType)baseMessage będzie lepszy od var message = baseMessage as SpecificType. Jest to punkt szczególny, ponieważ rzut statyczny nie może ci pomóc w twojej sprawie.

Jak już wspomniano, dwie powyższe czynności wykonano w sposób polimorficzny przy użyciu wzorca projektowego, który może być najlepszym rozwiązaniem, ponieważ dodaje tylko wywołanie metody wirtualnej. Wyodrębnienie wspólnej metody do abstrakcyjnej klasy (lub wspólnego podpisu metody do interfejsu) jest zdecydowanie najbardziej eleganckim rozwiązaniem. Istnieje obciążenie związane z wywołaniem metody wirtualnej, ale można to złagodzić, zaznaczając konkretne metody na typach pochodnych za pomocą słowa kluczowego sealed.

Wreszcie, jeśli to możliwe, stosuj generyczne, aby wyeliminować rzuty, ponieważ metody ogólne są optymalizacją w czasie kompilacji, w przeciwieństwie do rzutowania w czasie wykonywania.

poważaniem,

Powiązane problemy