views:

405

answers:

8

Hi,

I was answering a question a few minutes ago and it raised to me another one:

In one of my projects, I do some network message parsing. The messages are in the form of:

[1 byte message type][2 bytes payload length][x bytes payload]

The format and content of the payload are determined by the message type. I have a class hierarchy, based on a common class Message.

To instantiate my messages, i have a static parsing method which gives back a Message* depending on the message type byte. Something like:

Message* parse(const char* frame)
{
  // This is sample code, in real life I obviously check that the buffer
  // is not NULL, and the size, and so on.

  switch(frame[0])
  {
    case 0x01:
      return new FooMessage();
    case 0x02:
      return new BarMessage();
  }

  // Throw an exception here because the mesage type is unknown.
}

I sometimes need to access the methods of the subclasses. Since my network message handling must be fast, I decived to avoid dynamic_cast<> and I added a method to the base Message class that gives back the message type. Depending on this return value, I use a static_cast<> to the right child type instead.

I did this mainly because I was told once that dynamic_cast<> was slow. However, I don't know exactly what it really does and how slow it is, thus, my method might be as just as slow (or slower) but far more complicated.

What do you guys think of this design ? Is it common ? Is it really faster than using dynamic_cast<> ? Any detailed explanation of what happen under the hood when one use dynamic_cast<> is welcome !

--- EDIT ---

Since some people asked why:

Basically, when I receive a frame, I do two things:

  1. I parse the message and build a corresponding instance of a subclass of Message if the content of the frame is valid. There is no logic except for the parsing part.
  2. I receive a Message and depending on a switch(message->getType()), I static_cast<> to the right type and do whatever has to be done with the message.
+4  A: 

The only correct answer to "is it faster" is "try it."

MadKeithV
Unless someone you trusted has already done so, and is willing to share what they found?
djna
No, trying it is a much more robust solution that can be applied to many instances of the question "is it faster?"
MadKeithV
Testing is the practice. Here I'd like to focus on the theory ;) I'm less interested into the results than into the "how" here.
ereOn
The answer to the theory is "I'm sure the compiler writers did a good job of writing a dynamic_cast that works correctly and is near optimal for the general case".However, passing around classes over networking may surprise you - do you have FULL control over the client and the server? If there's any compiler / version mismatch between the two, any cast on the message may fail.
MadKeithV
@MadKeithV: Thanks. I should have mentionned that I do **not** serialize my message directly from memory to the network. The underlying frame is built in a host independant manner.
ereOn
Cool, in that case, if you have a very *specific* case (such as no real class hierarchy), you might actually be able to do something slightly faster than dynamic_cast.
MadKeithV
Keith, that last one is a bit too vague of a statement to be useful.
Stefan Monov
+2  A: 

A) This sounds very much like premature optimization.

B) If your design requires so many calls to dynamic_cast<> that you're worried about it, then you definitely need to take a look at your design and figure out what's wrong with it.

C) Like the previous answer said, the only way to answer whether it's faster is to use a profiler (or equivalent) and do a comparison.

jwismar
Thank you for your answer. I unfortunately could not test this under *heavy load stress* yet since I'm not done writing the rest. I mainly wanted to know if someone already used a similar design and **how** slower was `dynamic_cast<>`.
ereOn
Further to (B) I'd suggest that it would have to be a *lot* of calls to `dynamic_cast` to become non-neglible compared to the cost of the IO in the first place.
Phil Nash
@Phil Nash: I have one or two casts per received message, tops. I guess the real question should probably be: "Is `dynamic_cast<>` slower than a one byte to one byte comparison ?"
ereOn
I think the "real question" is "is one or two `dynamic_cast`s significant compared to IO?
Phil Nash
I will don't use a dynamic_cast when I don't need it it don't spend too much time. This is over protection.
Vicente Botet Escriba
+2  A: 

This depends on how you manage your messages. When I have a switch to select the message based on the type the best option is to use static_cast, as you know that the function parser will give you the create the correct type.

Message* gmsg parse(frame);

switch (gmsg->type) {
  case FooMessage_type:
    FooMessage* msg=static_cast<FooMessage*)(gmsg);
    // ...
    break;
  case BarMessage_type:
    BarMessage* msg=static_cast<BarMessage*)(gmsg);
    //...
    break;      
};

The use of dynamic_cast here is overprotecting.

Why do you need that all the messages inherits from a common one? What are the commonalities? I will add another design which doesn't use inheritance at all

switch (frame::get_msg_type(aframe)) {
  case FooMessage_type:
    FooMessage msg=parse<FooMessage)(aframe);
    // work with msg
    break;
  case BarMessage_type:
    BarMessage msg=parse<BarMessage)(aframe);
    //...
    break;
};

where parse parses a frame as a MSG, or throw an exception when the parse fails.

I see other answer that are telling you to use virtual functions. I really don't see any advantage to this OO design for messages.

Vicente Botet Escriba
Actually, this describes **exactly** what i'm doing. I did this mainly to avoid the "overprotection" you're talking of.
ereOn
So you don't need dynamic_cast as you know already the class.
Vicente Botet Escriba
A: 

You already have an abstract base class "Message". Use it as an interface to hide the implementation details of FooMessage and BarMessage.

I guess, that is why you have chosen that approach, or isn't it?

The base class is used mainly because most of the time, I want to deal with Messages in a global manner. However, sometimes, I **need** to access to the child class methods/members.
ereOn
Then you have to get your stop watch ... as someone else already pointed out. :-)
+6  A: 

Implementations of dynamic_cast will of course vary by compiler.

In Visual C++, the vtable points to a structure which contains all of the RTTI about a structure. A dynamic_cast therefore involves dereferencing this pointer, and checking the "actual" type against the requested type, and throwing an exception (or returning NULL) if they are not compatible. It is basically equivalent to the system you describe. It's not particularity slow.

Your design also sounds a bit off - you have a factory method which forgets the true type of an object, then you immediately want to un-forget that information. Perhaps you should move that logic you do when unforgetting a type into the factory method, or into virtual methods on the base class itself.

Terry Mahaffey
+1: Because you give a nice description of what happens exactly on a "call" to `dynamic_cast<>`. For the last part of your answer, I edited my question to give additional information on why I choosed this design.
ereOn
I don't think that using virtual functions will be useful; virtual methods of which class?
Vicente Botet Escriba
Virtual function(s) on the Message class, implemented in the various subclasses, containing any logic specific to that subclass needed when processing that Message
Terry Mahaffey
+3  A: 

When people say dynamic_cast is slow it is just a rule of thumb. dynamic_cast more or less does what you are doing. It is slow because it involves a couple of memory accesses. Kind of like when people say virtual functions are slow. You are taking something fast(a function call) and adding a couple of memory accesses to it. It is a significant slowdown(since all the way to ram and back can take a few hundred cycles), but for most people it just doesn't matter as long as it isn't done often(with a very large value of often).

stonemetal
+1  A: 

You are focusing on the speed, but what of the correctness ?

The underlying question is are you sure you won't make a mistake ? In particular, you might be tempted to wrap the casting method in such a way:

template <class T>
T* convert(Message* message)
{
  if (message == 0) return 0;
  else return message->getType() == T::Type() ? static_cast<T*>(message) : 0;
}

In order to embed the test and cast in a single function, thus avoiding error such as:

switch(message->getType())
{
case Foo:
{
  //...
  // fallthrough
}
case Bar:
{
  BarMessage* bar = static_cast<BarMessage*>(message); // got here through `Foo`
}
}

or the obvious:

if (message->getType() == Foo) static_cast<BarMessage*>(message); // oups

Not much admittedly, but it's not much effort either.

On the other hand you can also review your technic by applying runtime dispatch.

  • virtual methods
  • Visitor

etc...

Matthieu M.
I can't make a mistake: if the message has a given "type byte", it means it was succesfully parsed and its byte type matches.
ereOn
I was thinking of testing for a type and mistakenly casting to another. I know it sounds stupid, but we are human, and that's certainly the kind of things I would risk to do :)
Matthieu M.
Well, **this** way I can make a mistake, for sure ;) But easy enough to be spotted and fixed quickly ;)
ereOn
Which functions will be virtual on on which class?
Vicente Botet Escriba
talking of possible errors. The parse function can also create the bad class associated to the type found.
Vicente Botet Escriba
A: 

I don't see any answers touching on this, but you can't send C++ objects over the network and expect them to arrive intact. The virtual table is set up based on the state of memory in the sending computer, it is quite likely that the receiving computer will not have things in the same place. This will also typically make RTTI fail (which is what dynamic_cast uses) since RTTI is often implemented along with the vtable.

dash-tom-bang
I don't copy the memory directly to the network. The frames are built according to a protocol specification in a host-independant manner.
ereOn