This invention relates to distributed systems that employ a publish-subscribe data exchange in which applications publish data samples, which become available to remote applications interested in them. Specifically, this invention relates to Data Distribution Service (DDS) for Real-time Systems which is a specification of a publish/subscribe middleware for distributed systems.
Data Distribution Service and Type System
Distributed systems typically employ a publish-subscribe data exchange in which applications publish data samples, which become available to remote applications interested in them. Data Distribution Service (DDS) for Real-time Systems is a specification of a publish/subscribe middleware for distributed systems. As a networking middleware, DDS simplifies network programming. DDS implements a publish/subscribe model for sending and receiving data samples among software applications. The software applications create publishing and subscribing endpoints.
The publishing endpoints, called DataWriters, publish data samples for a Topic (e.g. image, temperature, location). The subscribing endpoints, called DataReaders, receive the data samples published by the DataWriters on a Topic.
DDS takes care of delivering the data samples to all DataReaders that declare an interest in a Topic. Delivering a sample requires marshalling the sample. Marshalling is the process of transforming the memory representation of a data sample to a serialized representation suitable for transmission over the network.
In DDS, the memory representation of a data sample is determined by the language binding. The language binding specifies the programming-language mechanisms an application can use to construct and introspect data samples. For a giving programming language there can be multiple language bindings.
In DDS, the serialized network representation (or wire representation) of a data sample is called Extended Common Data Representation (CDR), and there are two versions: Extended CDR (encoding version 1) (XCDR1) and Extended CDR (encoding version 2) (XCDR2). The representations are defined in the Object Management Group (OMG) Extensible and Dynamic Topic Types for DDS (DDS-XTYPES) specification.
Each publishing or subscribing endpoint (DataWriter or DataReader) is associated with a type that defines the structure for all data samples sent by the DataWriter or received by the DataReader. DDS-XTYPES describes the DDS Type System comprising the following types:
The top-level types, or the types that can be associated directly with a Topic, are structures or union types. Types can be described in multiple languages including XML (eXtensible Markup Language) and IDL (Interface Definition Language). For example, the following IDL snippet defines a type for a camera image topic called CameraImage. This type is a structure type that contains members that have themselves a type:
A data sample for the type above is a concrete camera image.
Types can have some of their members marked with annotations. There are four important annotations for a member that are relevant to this invention:
The Extended CDR, used to send and receive data samples on the network, supports data type evolution. That is, it allows a data type to change in certain well-defined ways without breaking communication. Types can be marked with the following annotations to indicate the kind of evolution that they support:
To support type evolution, the wire representation defines a set of headers that can be added at the beginning of an aggregated type (DHEADER), before each member within the aggregated type (EMHEADER), and at the end of an aggregated type (SENTINEL).
Among other information, the headers encode the size of the content they apply to. EMHEADERs also include a unique ID for a member in a mutable type. The SENTINEL header is only applicable to mutable types with Extended CDR (encoding version 1).
DHEADERs allow extending a type by adding members at the end. Types marked as appendable and mutable add a DHEADER at the beginning when the network representation is XCDR2. For example:
Position2D wire format is shown in
EMHEADERs allow evolving a type by adding members in the middle or reordering existing members. The header is applicable to mutable types.
Position2D wire format is shown in
Existing Data Sample Manipulation Approaches
There are two ways in which a DDS implementation can manipulate and work with data samples:
Functional Data Sample Manipulation
A Code Generator application takes the type definition (e.g., IDL definition) as an input, and it generates a set of type-specific functions in the programming language of the DDS implementation. There are N functions per (type, language) pair where N is the number of operations (e.g. marshal, unmarshal) that can be done on data samples for the type. The set of functions generated to manipulate samples of a type are called TypePlugin functions. The process of functional data sample manipulation is shown in
Among other functions, when the Code Generator runs, it will generate the functions to marshal (serialize) and unmarshal (deserialize) a data sample with type Position:
The main advantage of doing Functional data sample manipulation is performance. The time required to manipulate data samples is minimized because the generated functions are tailored to the specific types for which they are generated.
There are two disadvantages:
TypeDescription-Driven Interpreted Data Sample Manipulation
With this approach, there are no type-specific functions. Instead, general functions receive as a parameter a description of the type generated by a Code Generator application or built at run-time programmatically. These general functions are called interpreter functions, and there is one per operation on data samples. The process of TypeDescription-Driven Interpreted data sample manipulation is shown in
For example:
There are two main advantages of doing TypeDescription-Driven Interpreted data sample manipulation:
The main disadvantage of using this approach is performance, because the general interpreter functions do not have a priori knowledge of a type and cannot be specialized for each type.
The present invention addresses these disadvantages and provides technology to manipulate data samples of a topic type that does not have the maintainability and size issues of the “Functional Data Sample Manipulation” approach and it does not have the performance issues introduced by the “TypeDescription-Driven Interpreted Data Sample Manipulation” approach.
Recipes: A recipe contains a list of instructions that indicate how to perform an operation on a data sample (e.g. serialize the sample). An instruction operates on a member or a set of members in a data sample. The instruction includes an opcode and a set of parameters. A parameter can refer to a recipe if it is a parameter of a complex instruction.
DDS Type (or Type): Defines a common structure for data samples of a DDS Topic. DDS-XTYPES OMG specification describes the DDS Type System. The type of a data sample is normally composed of a list of members. For example:
Structure: A Structured type is a named type composed of a list of members.
Union: A Union type is a named type that contains a discriminator member and a list of value members from which at most one can be active based on the discriminator value.
Alias: An alias type provides an alternative name for a structure or union.
TypeDescription: A description of a Type understandable by a computer program.
Memory representation: Represents how a sample is laid out in memory and it is determined by the language binding.
Language binding: specifies the programming-language mechanisms an application can use to construct and introspect data samples. For a given programming language there can be multiple language bindings.
Network representation (or Wire representation): Represents how a sample is transmitted on the network. In DDS, the serialized network representation of a data sample is called Extended Common Data Representation (CDR), and there are two versions: Extended CDR (encoding version 1) (XCDR1) and Extended CDR (encoding version 2) (XCDR2). The representations are defined in the OMG Extensible and Dynamic Topic Types for DDS (DDS-XTYPES) specification.
This invention is a method and system for manipulating data samples to perform bidirectional conversion between a memory representation and a network representation of data samples associated with a DDS type in a system using the OMG Data Distribution Service (DDS) and the Real-Time Publish Subscribe (RTPS) protocol. The process of bidirectional conversion between a memory and a network representation is shown in
The method to convert between a memory representation and a network representation is based on the generation of recipes using a TypeDescription of data samples and information specific to the language binding that describes how a data sample is represented in memory (TypeOffsets). Recipes are generated once, at run-time or by a Code Generation application, and they are executed for every data sample. The process of recipe generation and recipe execution for bidirectional conversion between a memory and a network representation is shown in
The method can be further embodied, access of a data sample in a memory representation is independent of a target language binding by the following mechanisms:
The method can be further embodied by a generated recipe containing a set of instructions independent of the target language binding that indicate how to perform an operation on a data sample, for example by the following mechanisms:
The method can be further embodied by multiple versions of a recipe can be generated for the same Type using different RecipeProperties to work with different network representations coexisting in the DDS system, for example by the following mechanisms:
The method can be further embodied by a recipe generated with different optimizations levels to reduce the execution time and improve communication latency, for example by the following mechanisms:
A recipe can be generated with no optimization when there is a one-to-one mapping between a member in a type and a recipe instruction.
The method can be further embodied by a recipe for a mutable Type containing a member index that reduces the amount of time required to find the instruction that will process a member value in the network representation of a data sample, for example by the following mechanisms:
The method can be further embodied by a recipe for a union Type containing a discriminator index that reduces that amount of time required to find the instruction that will process the value of the active member in the network representation of a data sample, for example by the following mechanisms:
Where the discriminator index can be implemented using a variety of data structures, including hash tables, B-Trees, and ordered lists of member IDs.
The method can be further embodied by executing the recipes generated by the second method on a data sample in which a serialization recipe is executed by:
The method can be further embodied where a deserialization recipe is executed by:
The method can be further embodied where the get_max_serialized_size and get_min_serialized_size recipes are executed a single time after the recipe generation method is applied because they do not operate on the data sample but on the data Type.
The invention can also be described as a method for manipulating data samples associated with a DDS type in a system using an Object Management Group (OMG) Data Distribution Service (DDS) and a Real-Time Publish Subscribe (RTPS) protocol, the method steps including:
Embodiments of this invention can be in the form of a method, system, computer-implemented method executable by computer hardware, computer code where methods steps are executable by a computer processor, distributed over the Internet where the system or method steps are executed by a computer server, or the like.
The embodiments of this invention provide a method or system to manipulate data samples of a topic type and convert between a memory and a network representation for a data sample that is more performant, maintainable, and has a smaller source code footprint than the existing approaches.
Recipe-Driven Interpreted Data Sample Manipulation
To address the performance issues of the TypeDescription-Driven Interpreted data sample manipulation, this invention provides a method for data sample manipulation based on the generation of a set of instructions per operation on data samples called a recipe. The recipes are executed by general purpose functions in the target programming language called recipe execution functions.
The IDL types are preprocessed by a Code Generation application that generates one or more recipes (see Recipe Set) per data sample operation.
The process of recipe-driven data sample manipulation is shown in
For example:
The generation of recipes after preprocessing the IDL type allows applying multiple performance optimizations that in many cases will make the data sample operations run faster than with a Functional data sample manipulation approach (see Functional Data Sample Manipulation).
In addition, there is no need to generate per-type functions, keeping the code size small.
Recipe Functions
The Recipe-Driven Interpreter includes the following recipe execution functions:
Recipe
A recipe contains a list of instructions that indicate how to perform an operation on a data sample (e.g., serialize the sample) (
Instructions
There are four kinds of instructions:
The following type declaration shows the different member types in a structure called “MyStruct”:
Primitive Instructions
The primitive instructions are these:
The parameters of a primitive instruction are these:
String Instructions
The string instructions are these:
The parameters of a string instruction are these:
Complex Instructions
The complex instructions are these:
The parameters of a complex instruction are these:
Header Instructions
The header instructions are these:
The parameters of a complex instruction are these:
Accessing a Data Sample Value in Memory Representation
The manipulation of data samples requires accessing their member values in the memory representation. This is done by using the type offsets. A type offset is a 64-bit integer that points to a memory address in the application memory space relative to the start memory address of the data sample (
Recipe functions use type offsets to locate member values for data samples (
For example, for the type Position below and the C programming language, the offset for “x” is 0 and for “y” is 4.
If the memory representation of a data sample member value (e.g. “x” for Position) is equal to the network representation, the recipe functions can directly access the member value at the memory address (sample address+member value offset). However, in some cases, for some programming languages, the memory representation of a member value may be different (in size and/or format) than the network representation.
For example, the memory representation for the “x” member in the Position type in a programming language could be an 8-byte integer, while the network representation is a 4-byte integer. In this case, the recipe function cannot directly access the member value at the memory address (sample address+x offset), and it must obtain this value by using a sample accessor.
Sample accessors are set per member, and they implement an interface (
Sample accessors allow keeping the implementation of the recipe functions (e.g, GeneralPlugin_serialize) generic and independent of the programming language. If the memory representation for a sample member in a target language is different than the wire representation, the Code Generator application must generate sample accessors for the member. These sample accessors, in combination with the member offset, will allow getting the member value when serializing and allow setting the member value when deserializing.
Customizing TypePlugin Functions
In some cases, the user may want to intercept the processing of a data sample member while running a recipe for the data sample. For example, let's assume the type Position below:
When the recipe function GeneralPlugin_serialize is run to serialize a data sample, the user may want to report an error if the current Position is outside a given area. To allow doing this, the instructions that process a complex member (e.g., SER_COMPLEX) receive an optional parameter called UserTypePlugin. The UserTypePlugin defines an API (
Before the SER_COMPLEX instruction for a Position object is executed, GeneralPlugin_serialize will invoke the UserTypePlugin function serialize to customize the serialization behavior.
For each input type, the Code Generator application generates skeletons for the UserTypePlugin API for the type. By default, these skeletons have an empty implementation.
Recipe Generation
Recipe generation is the process of taking a type and generating a set of recipes to manipulate the data samples for the type. For example, the serialization recipe for a camera image is as shown in
Data sample manipulation recipes can be generated at code generation time or at run-time when a type is registered with a DDS application.
The recipes for a given type are just data. The execution of a recipe is done by a recipe function (see Recipe Functions). For example, the Recipe type in the C programming language is defined as follows:
The recipe generation algorithm (RecipeGenerator_generate_recipe), as shown in
Recipe Generation Properties
The recipe generation properties configure several aspects of the recipe generation process:
Recipe Optimization Levels
The cost of serialization and deserialization operations increases with type complexity and sample size, and it can become a significant contributor to the latency required to send and receive a sample.
The OptimizationLevel generation property allows configuring three different levels of optimizations for recipe generation.
Level 0: There is no optimization. There is a one-to-one mapping between a member in a type and a recipe instruction. For example:
The serialize recipe for CameraImage is as follows:
CameraImage Recipe:
SER_PRIMITIVE(Offset: 0, Count: 1, . . . )
SER_PRIMITIVE(Offset: 4, Count: 1, . . . )
SER_COMPLEX(Offset: 8, Count: 1, . . . ) // Run Recipe for Resolution
SER_PRIMITIVE_SEQ(Offset: 16, Count: 1, . . . )
Resolution Recipe:
SER_COMPLEX(Offset: 0, Count: 1, . . . ) // Run Recipe for Dimension
Dimension Recipe:
SER_PRIMITIVE(Offset: 0, Count: 1, . . . )
SER_PRIMITIVE(Offset: 4, Count: 1, . . . )
Running the recipe for CameraImage will require executing 7 instructions (4 for Camera Image, 1 for Resolution, and 2 for Dimension).
With optimization level 0, the recipe generation's goal is to reduce the cost of generating a recipe and to allow the user to intercept the processing of a data sample to customize the behavior (see Customizing TypePlugin Functions).
Level 1: The recipe generation function optimizes the recipe execution time by resolving typedef (aliases). If a member type is a typedef that can be resolved to a primitive, enum, or aggregated type (struct, union, or value type), the recipe generation function will not generate an instruction for the typedef, but generate the type to which it resolves to. For the previous example, the recipe would be:
CameraImage Recipe:
SER_PRIMITIVE(Offset: 0, Count: 1, . . . )
SER_PRIMITIVE(Offset: 4, Count: 1, . . . )
SER_COMPLEX(Offset: 8, Count: 1, . . . ) // Run Recipe for Dimension
SER_PRIMITIVE_SEQ(Offset: 16, Count: 1, . . . )
Dimension Recipe:
SER_PRIMITIVE(Offset: 0, Count: 1, . . . )
SER_PRIMITIVE(Offset: 4, Count: 1, . . . )
Running the recipe for CameraImage will require executing 6 instructions (4 for Camera Image and 2 for Dimension).
With optimization level 1, the user will not be able to customize behavior for typedef serialization/deserialization since the typedef recipe is not run. However, the user will still be able to customize behavior for CameraImage and Dimension.
Level 2: With this optimization level, the recipe generation function optimizes the generation of recipes for structures and valuetypes by using more aggressive techniques. These techniques include:
The goal of optimization level 2 is to provide the fastest execution time for a recipe. Depending on the type layout and complexity, the serialization/deserialization can be several times faster than with a traditional Functional data sample manipulation approach (see Functional Data Sample Manipulation).
Inline Expansion of Nested Types
Inline expansion is an optimization in which the recipe generator replaces a type definition with another type definition in which nested types are flattened out. This is done to remove the execution of complex instructions during serialization/deserialization. For example:
With optimization level 2, the recipe generator replaces the definition of CameraImage with the following equivalent definition:
This optimization is only done for recipe generation purposes. The language binding (e.g., C++) mapping of CameraImage is still the same and continues using Resolution.
For the previous example, after applying the optimization the recipe will be:
SER_PRIMITIVE(Offset: 0, Count: 1, . . . )
SER_PRIMITIVE(Offset: 4, Count: 1, . . . )
SER_PRIMITIVE(Offset: 8, Count: 1, . . . )
SER_PRIMITIVE(Offset: 12, Count: 1, . . . )
SER_PRIMITIVE_SEQ(Offset: 16, Count: 1, . . . )
Running the recipe for CameraImage will require executing 5 instructions as opposed to 7 without optimizations.
Processing Multiple Members with a Single Instruction
If the memory layout (e.g. C++ layout) of a set of consecutive primitive members in a data sample matches the network layout considering the data representation (XCDR1 or XCDR2) and the endianness (BIG endian or LITTLE endian) and there is no padding between the different member values (in memory and network), the recipe generator can generate a single instruction to process all the members versus one per member.
In the previous example the first four instructions would be coalesced into a single instruction with a count value of 16 (4 integers), a primitive size of 1, and a primitive alignment of 4:
SER_PRIMITIVE(Offset: 0, Count: 16, PrimitiveSize: 1, PrimitiveAlignment: 4 . . . )
SER_PRIMITIVE_SEQ(Offset: 16, Count: 1, . . . )
When the primitive instructions are coalesced, the resulting instruction considers the primitive type an octet and sets the primitive alignment to the alignment of the primitive type of the first member in the group.
Running the recipe for CameraImage will require executing 2 instructions versus 7 without optimizations.
The combination of both optimizations, first inline expansion and second coalescing of multiple members into a single instruction provides the fastest execution time. However, the user loses the ability customize the data manipulation process as there is no 1-1 correspondence between members and instructions (see Customizing TypePlugin Functions).
Rules for Inline Expansion
There are two fundamental approaches to determine when to apply inline expansion:
The main disadvantage of the first approach is memory usage, because the recipe for two or more members with the same complex type requires the generation of an independent set of instructions. For example:
By applying inline expansion based on rule 1, one would end up with the following type:
The serialization recipe for the type TopLevelType would be:
TopLevelType Recipe.
SER_STRING(Offset: 0, Count: 1, . . . ) SER PRIMITIVE(Offset: 4, Count: 1, . . . )
SER_PRIMITIVE(Offset: 8, Count: 1, . . . )
SER_STRING(Offset: 12, Count: 1, . . . )
SER_PRIMITIVE(Offset: 16, Count: 1, . . . )
SER_PRIMITIVE(Offset: 20, Count: 1, . . . )
SER_STRING(Offset: 24, Count: 1, . . . )
SER_PRIMITIVE(Offset: 28, Count: 1, . . . )
SER_PRIMITIVE(Offset: 32, Count: 1, . . . )
Offsets are based on a C++ memory representation in a 32-bit architecture.
Without inline expansion the serialization recipe for the TopLevelType would be:
NestedType Recipe:
SER_STRING(Offset: 0, Count: 1, . . . )
SER_PRIMITIVE(Offset: 4, Count: 1, . . . )
SER_PRIMITIVE(Offset: 8, Count: 1, . . . )
TopLevelType Recipe:
SER_COMPLEX(Offset: 0, Count: 1, . . . )
SER_COMPLEX(Offset: 12, Count: 1, . . . )
SER_COMPLEX(Offset: 24, Count: 1, . . . )
The recipe contains 6 instructions as opposed to 9 instructions with inline expansion, translating into higher memory usage for the TopLevelType recipe.
If inline expansion was done only for structures in which the memory layout matches the network layout, the instructions for the different members could be coalesced into a single primitive instruction (see Processing Multiple Members with a Single Instruction). This single primitive instruction would not only improve the performance of the recipe execution, it would also reduce the memory usage for the recipe of the top-level type. For example:
Because the memory layout for NestedType matches the network layout, NestedType can be expanded, resulting in the following top-level type:
The processing of all the members in TopLevelType can be done with a single recipe instruction, because memory and network layouts match:
SER_PRIMITIVE(Offset: 0, Count: 36, PrimitiveSize: 1, PrimitiveAlignment: 4 . . . )
We have not only optimized the recipe execution time, we have also reduced the memory consumption by going from 6 instructions before inlining to 1 instruction after inlining and coalescing instructions.
Algorithm for Inline Expansion Based on Memory and Network Layout Match
To see if inline expansion can be applied to a struct/valuetype ‘T’, it is necessary to determine if the memory layout (e.g., C, C++) for a data sample of type ‘T’ matches the network layout (XCDR1 or XCDR2). The layouts match when all of the following conditions apply:
For the C++ language binding, the sizes are also as above except in some platforms where the size of the boolean is different. In this case, the algorithm detects the difference. When the boolean is different, structures containing the booleans are not inlinable.
Other language bindings may require different size tables.
The C size and alignment assumes default packing and alignment. For example:
Other language bindings may require different alignment tables.
For serialization and deserialization purposes, the RecipeGenerator_generate_recipe function will consider an inlinable structure (according to the previous rules) as a primitive array, where the alignment of the primitive type corresponds to the alignment of the first member of the structure. A member with type ‘T’ will be serialized with a single copy (memcpy) invocation.
Alignment Optimizations
The algorithm to generate recipes (RecipeGenerator_generate_recipe) also tries to save alignment operations when possible. For example:
For the previous type, if the endianness of the network and memory layouts is not the same, the recipe generation function cannot apply the optimization in which height and width are processed together with a single instruction. In such a case, the recipe for serialization would be like this:
Dimension Recipe:
SER_PRIMITIVE(Offset: 0, Count: 1, PrimitiveAlignment: 4, . . . )
SER_PRIMITIVE(Offset: 4, Count: 1, PrimitiveAlignment: 2, . . . )
By default, the execution of the SER_PRIMITIVE instruction will try first to align the memory in the network buffer to an address divisible by the PrimitiveAlignment parameter of the SER_PRIMITIVE instruction that is being processed. After that, the execution will “copy” the value from the memory buffer to the network buffer. The copy operation will require byte swapping because the endianness between the network and memory representation is different.
The alignment operation is a costly operation because it requires several arithmetic operations:
In the previous type, however, RecipeGenerator_generate_recipe knows that the member “width” is already aligned after “height” is processed, because the PrimitiveAlignment “width” is smaller than the primitive alignment for “height”. In this case, when the SER_PRIMITIVE for width is generated, the parameter MustAlign is set to FALSE, indicating that alignment is not necessary at execution time:
Dimension Recipe:
SER_PRIMITIVE(Offset: 0, Count: 1, PrimitiveAlignment: 4, MustAlign: TRUE . . . )
SER_PRIMITIVE(Offset: 4, Count: 1, PrimitiveAlignment: 2, MustAlign: FALSE . . . )
Instruction Index
Member ID Indexes
Types marked as @mutable can evolve by reordering, adding, or removing members. The next example shows how the type Position evolves by adding a new member “z” to the Position type. This member is not added at the end but in the middle.
To allow this kind of evolution, the XCDR network representation serializes each one of the member values (e.g. x, y) by prepending an EMHEADER. This header contains information including the member ID (a unique 32-bit integer identifying the member), and the size of the serialized member value following the header.
The member ID allows locating a member in the network buffer. The size allows moving to the next member. Position2D wire format as shown in
Assume a DataWriter publishing Position2D and a DataReader subscribing to Position3D.
The serialization recipe for Position2D assuming XCDR2 wire format is:
Position2D Serialization Recipe:
SER_DHEADER( )
SER_MEMBER_HEADER(MemberId: 1, . . . )
SER_PRIMITIVE( )
SER_MEMBER_HEADER(MemberId: 2, . . . )
SER_PRIMITIVE( )
The deserialization recipe for Position3D assuming XCDR2 wire format is:
Position3D Deserialization Recipe:
DESER_DHEADER( )
DESER_MEMBER_HEADER(MemberId: 1, . . . )
DESER_PRIMITIVE( )
DESER_MEMBER_HEADER(MemberId: 3, . . . )
DESER_PRIMITIVE( )
DESER_MEMBER_HEADER(MemberId: 2, . . . )
DESER_PRIMITIVE( )
When a DataReader receives a Position2D data sample, the GeneralPlugin_deserialize function, which deserializes the sample by executing the Position3D recipe, cannot execute the instructions in order. If it did, the value y would be deserialized as z. Instead, the GeneralPlugin_deserialize must locate the instruction that deserializes a member value by using the member ID.
To make this search efficient, the RecipeGenerator_generate_recipe function will create a member ID index that will map a member ID to an instruction index. The index will be stored with the recipe (
Instruction indexes can be implemented using a variety of data structures, including hash tables, B-Trees, and ordered lists of member IDs.
Member ID indexes for mutable types are generated only for the following recipes: DESER_RECIPE, SER_TO_KEY_RECIPE, and SKIP_RECIPE.
Union Discriminator Indexes
Unions define a well-known discriminator member and a set of type-specific members. The discriminator member is always considered to be the first member of a union. Each type-specific member is associated with one or more values of the discriminator. These values are identified in one of two ways:
For example:
The serialization recipe for ParameterValue is:
ParameterValue Serialization Recipe.
SER_PRIMITIVE(Kind=ENUM, . . . ) // Discriminator serialization
SER_PRIMITIVE(Kind=OCTET)
SER_PRIMITIVE(Kind=SHORT)
SER_PRIMITIVE(Kind=LONG)
When this program is executed, the GeneralPlugin_serialize only needs to serialize the value of the discriminator in the data sample, and the value of the member that is selected by the discriminator. For example, if the discriminator value is SHORT, the wire representation would be as shown in
To make the selection of the member value instruction efficient, the RecipeGenerator_generate_recipe will create an index that will map a discriminator value to a member instruction index. The index will be stored with the recipe.
Discriminator indexes can be implemented using a variety of data structures, including hash tables, B-Trees, and ordered lists of possible discriminator values.
Discriminator indexes for unions are generated when any of the following conditions apply:
Single-Value Recipes
The following recipes generated by the function RecipeGenerator_generate_recipe do not receive a data sample as a parameter: GET_MAX_SER_SIZE_RECIPE and GET_MIN_SER_SIZE_RECIPE. As such, the result of executing the recipe is always the same: a single number representing the maximum serialized size of a data sample (GET_MAX_SER_SIZE_RECIPE) and a single number representing the minimum serialized size of a data sample (GET_MIN_SER_SIZE_RECIPE).
It does not make sense for the application to keep these recipes around after they run once. Because of that, when RecipeGenerator_generate_recipe generates the recipes, it executes them once, then stores the result as part of the recipe. Finally, it replaces all the instructions of the recipe with a single RETURN_PRIMITIVE. What is left is a single-value recipe with an O(1) execution time.
Recipe Generation Algorithm
In the flowcharts of
Recipe Execution
The recipes generated by RecipeGenerator_generate_recipe are executed by the recipe functions described in Recipe Functions.
Serialization Functions
The functions GeneralPlugin_serialize and GeneralPlugin_serialize_key receive the following parameters:
These functions have two different flows depending on whether or not there is an instruction index as shown in
The execute_instruction function executes an instruction. For serialization recipes, this involves:
Deserialization Functions
The functions GeneralPlugin_deserialize, GeneralPlugin_deserialize_key, and GeneralPlugin_serialized_sample_to_key receive the following parameters:
The algorithm is the same as the one for serialization. The only difference is in the execute_instruction operation, which copies the network value for a member into the memory representation.
Also, the instruction index can be a member ID index or a discriminator index. See Instruction Index for information about the conditions that trigger the generation of an instruction index.
Get Serialized Size Function
The function GeneralPlugin_get_serialized_size receives the following parameters:
The way the operation works is identical to the way the serialization functions work. Instead of having a real network buffer, this function uses a logical network buffer. It never copies values into the network buffer, but it advances the position.
Skip Function
The function GeneralPlugin_skip receives the following parameters:
If the recipe has a DHEADER instruction, the operation skips the data sample by advancing the network buffer position N-bytes, where N is the size contained in the DHEADER, which is serialized at the beginning of the NetworkBuffer.
If the recipe does not have a DHEADER, the way the operation works is identical to the way the deserialization functions work. Instead of copying the member values from the network buffer into the data sample, this function just advances the position of the buffer.
Get Max/Min Serialized Size Functions
The functions GeneralPlugin_get_max_serialized_size and GeneralPlugin_get_min_serialized_size do not operate on a data sample but on the type.
These functions are invoked first by the RecipeGenerator_generate_recipe function to generate single-value recipes. Single-value recipes are recipes with a single RETURN_PRIMITIVE instruction that returns a constant value. In this case, the value represents the maximum serialized size or the minimum serialized size of a data sample for a type. For additional information on single-value recipes, see Single-Value Recipes.
Optimized Execution Functions
To reduce the number of CPU cycles required to execute a recipe, the Recipe-Driven Interpreter may provide alternative execution functions that only work on types with certain properties. For example, assume this type:
The Position3D type is a type that only contains primitive members. These types are quite common in DDS type systems.
For types like the one in the previous example, the Recipe-Driven Interpreter may provide a new set of serialization/deserialization execution functions that only work with recipes on types containing only primitive members:
The RecipeGenerator_generate_recipe operation will store with the recipe the execution functions that should be used to execute the recipe. This decision is made at recipe generation time.
Recipe Set
The generation of recipes for a type may require generating different versions of the same recipe (e.g., DESER_RECIPE) for the type. For example, if a DataReader can receive both XCDR1 and XCDR2 wire representations, it will be necessary to generate two versions of the DESER_RECIPE, one for XCDR1 and one for XCDR2.
Variations of the same recipe are grouped together in a RecipeSet.
By default, a RecipeSet contains 8 different recipes that are the result of selecting the different combinations for (Endianness, RepresentationId, OnlyKey).
The user can limit the number of recipes on a RecipeSet as follows:
Resolution resolution;
By marking the type as XCDR2, the Code Generator application or the application at run-time will not have to generate XCDR1 recipes for the type.
Recipe-Driven Interpreted Data Sample Manipulation Performance
The generation of recipes after preprocessing the IDL type allows applying performance optimizations that in many cases will make the data sample operations run faster than with a traditional functional data sample manipulation approach (see Functional Data Sample Manipulation).
The following examples compare the performance of Connext DDS 6.0.0, which uses the recipe-driven data manipulation invention described in this document, with the performance of Connext DDS 5.3.1, which does Functional data sample manipulation. (Connext DDS is the DDS implementation of Real-Time Innovations.)
The serialization time for VitalSigns with Optimization Level 1 is shown in
The serialization time for Image with Optimization Level 2 is shown in
This application claims priority from U.S. Provisional Patent Application 62/811,739 filed Feb. 28, 2019, which is incorporated herein by reference.
Number | Name | Date | Kind |
---|---|---|---|
20070185591 | Frei | Aug 2007 | A1 |
20100115041 | Hawkins | May 2010 | A1 |
20110295923 | de Campos Ruiz | Dec 2011 | A1 |
20120246295 | Gonzalez-Banos | Sep 2012 | A1 |
Entry |
---|
Jose M.Lopez-Vega, “A content-aware bridging service for publish/subscribe environments”, Jan. 2013, Elsevier, vol. 86, pp. 108-124 (Year: 2013). |
Number | Date | Country | |
---|---|---|---|
20200278846 A1 | Sep 2020 | US |
Number | Date | Country | |
---|---|---|---|
62811739 | Feb 2019 | US |