Moving event data

January 13, 2022

When you send an event carrying some data object from one capsule to another, the data object will be copied by the TargetRTS. This is done so that the sender and receiver, who may run in different threads, cannot both access the data object at the same time. If the data object is big, and/or the event is sent frequently, this copying may lead to performance problems.

Starting with Model RealTime 11.1 2021.46 it is now possible to avoid the copying and instead move the data object. For this reason, a new type descriptor move function has been introduced. It has the same signature as the type descriptor copy function, except that the "source" parameter is non-const. If the type is a moveable C++ class, and you use a C++ 11 compiler, the move function will be automatically generated by the model compiler and will invoke the move constructor of the class. For example, a move function for a class "MyClass" will look like this:

static void rtg_MyClass_move(const RTObject_class* type, MyClass* target, MyClass* source)
{
    (void)new(target) MyClass( std::move(*source) );
}

Of course, you can write your own move function if you like. This can be useful if you want to move a data object typed by an externally defined type. You then create a typedef or type alias for the external type in the model, and then define a move function for it. Note that contrary to other type descriptor functions, the move function is optional. You only need to define it if you need to move an object of the type. Attempting to move an object of a non-moveable type will fallback to copying the object.

So how do you decide if you want an event data object to be copied or moved when sending the event? The answer is simple: if you pass an lvalue to the data object it will be copied, while if you pass an rvalue it will be moved. For example:

myPort.myEvent(data).send(); //Send by copy (lvalue ref to data)
myPort.myEvent(std::move(data)).send(); //Send by move (rvalue ref to data)

Sometimes the receiver needs to store the data object of a received event, to use it at a later point in time. This too by default requires copying the data object which otherwise only can be accessed in the transition triggered by the event. Also in this case you can choose to instead move the data object. For example, you can write code like this in a transition:

someAttr = std::move(*rtdata); // Avoid copying the message data object

For this to work, the "rtdata" parameter must be non-const. Otherwise it will anyway be copied. A new transition property has been introduced which allows you to use a non-const rtdata parameter.

So, how much can the performance improve by moving instead of copying event data? It's impossible to give an exact answer as it depends both on the data size and frequency of sending it. As a test we created a sample application that sends a data object 100 times. When the data size is below 4 kB the time difference is not big, but as the size grows moving starts to pay off.

We measured an approximate performance improvement of 35% for this application.

Finally, note that moving event data is only applicable for asynchronous communication (send), not synchronous communication (invoke). This is because synchronous communication never happens across threads, which means the TargetRTS doesn't need to copy the data. Hence, there is also no need to move it.