Messages and dynamic memory

Started by
9 comments, last by Hodgman 8 years, 3 months ago

Hi.

I am wondering about this. Is there a good way to do event messages between two large-scale loose-coupled parts of a game program without having to dynamically allocate and deallocate the messages? Messages may need to be queued, and there can be different types of messages, which suggests an inheritance hierarchy and that's where the dynamic memory stuff comes from, at least when using C++ as I'd be in this case. Thanks.

Advertisement

It depends on the variation in size of the messages, but you could use a union to ensure all your message fit in some space, and then make a ring buffer or a double linked list passing the data.

Another option is to override the new and delete operators, and provide a custom implementation of allocating and releasing memory for your objects, eg with a memory pool that you re-use.

Well you can send different size messages if theyre prefixed with a size or type.

Like serialize size followed by data (probably the raw memory) in a queue of bytes, and on the other end read the object into contiguous memory (since std::queue is not contiguous) and interpret the address as a Base*.

But unless you actually need something like that for performance reasons, I wouldnt do it because it seems a bit prone to bugs. Maybe if you do proper serialization instead of raw memcpy (or use POD types + switch instead of inheritance) its safer... (does c++ even allow raw memory copy of objects with virtuals?)

o3o

Well you can send different size messages if theyre prefixed with a size or type.

Like serialize size followed by data (probably the raw memory) in a queue of bytes, and on the other end read the object into contiguous memory (since std::queue is not contiguous) and interpret the address as a Base*.

But unless you actually need something like that for performance reasons, I wouldnt do it because it seems a bit prone to bugs. Maybe if you do proper serialization instead of raw memcpy (or use POD types + switch instead of inheritance) its safer... (does c++ even allow raw memory copy of objects with virtuals?)

Why would we serialize messages? This is about internal communication within the program.

Assuming there's a fairly limited number of different message classes with a high number of instances over time, object pooling might be a good choice. Allocate once, and instead of deallocating, free them to a pool from which you later get them once needed. It will never run out of messages and never allocate more money than needed.


Why would we serialize messages? This is about internal communication within the program.

Because it provides one solution to your problem. You can allocate a single block of memory and messages and write themselves into the buffer, etc...

First, I'd make sure you really need a messaging system anyway. Ideally you could replace them with method calls, and that would make the dependencies more clear.

If you really do need some sort of message-like design though (for instance, to be able to queue up messages and process them later), then ask yourself if dynamic allocation will really be a problem (I assume you're worried about performance of dynamic allocation).

Are you developing for mobile? As Phil said, I dont think the overhead of alloc/dealloc will be significant unless you literally create/destroy thousands of messages each frame and have a limited processing power. Don't over-optimize before you have to.

That being said, pooling is probably your first step solution for any alloc/dealloc optimizations. You can also stick to simple strings/enum/cachec const vars instead of a messaging class and pass the params as (templeted) function parameters rather than in the message object itself.

Comrade, Listen! The Glorious Commonwealth's first Airship has been compromised! Who is the saboteur? Who can be saved? Uncover what the passengers are hiding and write the grisly conclusion of its final hours in an open-ended, player-driven adventure. Dziekujemy! -- Karaski: What Goes Up...


Why would we serialize messages? This is about internal communication within the program.

Because it provides one solution to your problem. You can allocate a single block of memory and messages and write themselves into the buffer, etc...

First, I'd make sure you really need a messaging system anyway. Ideally you could replace them with method calls, and that would make the dependencies more clear.

If you really do need some sort of message-like design though (for instance, to be able to queue up messages and process them later), then ask yourself if dynamic allocation will really be a problem (I assume you're worried about performance of dynamic allocation).

Yes, I suppose that you could just use method calls, e.g. inherit from some kind of interface class which is kept suitably loose that implementation details don't leak, but then you can't defer processing until later, which was the purpose of the messaging system.

I am wondering about this. Is there a good way to do event messages between two large-scale loose-coupled parts of a game program without having to dynamically allocate and deallocate the messages? Messages may need to be queued, and there can be different types of messages, which suggests an inheritance hierarchy and that's where the dynamic memory stuff comes from, at least when using C++ as I'd be in this case. Thanks.

Big bloated generic "event" systems are overkill. Write something specific.
e.g. Producer and Consumer are decoupled, but an application wants one to forward an event to the other:
class Producer
{
public:
  Producer( const std::function<void(int)>& onFrobnicate )
    : onFrobnicate(onFrobnicate)
    , foo() {}

  void Frobnicate()
  {
    onFrobnicate(foo++);
  }
private:
  std::function<void(int)> onFrobnicate;
  int foo;
};
class Consumer
{
public:
  Consumer() : foo() {}
  void Foo( int x )
  {
    foo += x;
  }
private:
  int foo;
};
class Application1
{
public:
  Application1()
    : producer(std::bind( &Consumer::Foo, consumer, _1 ))
  { 
    for( int i=0; i!=10; ++i )
      producer.Frobnicate(); // calls consumer.Foo 10 times
  }
private:
  Consumer consumer;
  Producer producer;
}
Or if the application wants to buffer the events:
class Application2
{
public:
  Application2()
    : producer([&](int x){events.push_back(x);})
  { 
    //we expect to buffer 10 events, so let's pre-allocate that much memory as an optimization:
    events.reserve(10);

    for( int i=0; i!=10; ++i )
      producer.Frobnicate(); // pushes 10 results into 'events'

    //now send the buffered events to the consumer:
    for(auto i: events)
      consumer.Foo(i);
    events.clear();
  }
private:
  Consumer consumer;
  Producer producer;
  std::vector<int> events;
}
Isn't that easier than using some big inheritance-based messaging framework? C++ has tools for this already smile.png

To answer the other question -- if you did need to dynamically create a big stack of temporary messages, I'd use a pool allocator, or even better, a stack allocator. Stack allocators are the cheapest possible allocation of any algorithm, and are built around bulk destruction (freeing every allocation made at the same time).
In the above code, the vector acts like a stack allocator, which is a good start, and could be optimized further by replacing it with a hand-coded/optimized stack allocator.

Seconding Hodgman, I honestly consider "big hub" message exchange architectures lack of proper design. I don't recall a single case which, on closer scrutiny really required the arbitrary mappings.

While I support the notion above I would rather stay away from std::bind. To be honest I would go with an explicit 'onIntGenerated' callback or 'onIntListener' interface.

Previously "Krohm"

This topic is closed to new replies.

Advertisement