Skip to content

Commit

Permalink
Improve omgidl deserialization performance (#123)
Browse files Browse the repository at this point in the history
### Public-Facing Changes

Improve omgidl deserialization performance

### Description
This PR changes the `MessageReader` to build a tree structure of
deserialization info objects when constructed. When building this tree,
the concrete deserializers as well as other relevant type information
are looked up which avoids that this has to be done during the actual
deserialization. Further changes include the removal of some temporary
closures which reduces some GC time.

The outcome of these changes is a performance improvement of up to 30%
(depends on message type)


![image](https://github.com/foxglove/omgidl/assets/9250155/84e77541-f673-43fd-8c63-4fa0d4bd974f)
In the image above, **left** is this PR, **right** is the currently
released version

Resolves FG-6300
  • Loading branch information
achim-k authored Jan 31, 2024
1 parent 3082b94 commit 549c1f6
Show file tree
Hide file tree
Showing 4 changed files with 687 additions and 257 deletions.
255 changes: 255 additions & 0 deletions packages/omgidl-serialization/src/DeserializationInfoCache.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
import { IDLMessageDefinition } from "@foxglove/omgidl-parser";

import {
DeserializationInfoCache,
PRIMITIVE_ARRAY_DESERIALIZERS,
PRIMITIVE_DESERIALIZERS,
} from "./DeserializationInfoCache";

const TRANSFORM_DEFINITION: IDLMessageDefinition = {
name: "geometry_msgs::msg::Transform",
definitions: [
{ name: "translation", type: "geometry_msgs::msg::Vector3", isComplex: true },
{ name: "rotation", type: "geometry_msgs::msg::Quaternion", isComplex: true },
],
aggregatedKind: "struct",
};
const VECTOR_DEFINITION: IDLMessageDefinition = {
name: "geometry_msgs::msg::Vector3",
definitions: [
{ name: "x", type: "float64", isComplex: false },
{ name: "y", type: "float64", isComplex: false },
{ name: "z", type: "float64", isComplex: false },
],
aggregatedKind: "struct",
};
const QUATERNION_DEFINITION: IDLMessageDefinition = {
name: "geometry_msgs::msg::Quaternion",
definitions: [
{ name: "x", type: "float64", isComplex: false },
{ name: "y", type: "float64", isComplex: false },
{ name: "z", type: "float64", isComplex: false },
{ name: "w", type: "float64", isComplex: false },
],
aggregatedKind: "struct",
};
const TIME_DEFINITION: IDLMessageDefinition = {
name: "builtin_interfaces::Time",
definitions: [
{ name: "sec", type: "int32", isComplex: false },
{ name: "nanosec", type: "uint32", isComplex: false },
],
aggregatedKind: "struct",
};

const FLOAT64_PRIMITIVE_DESER_INFO = {
type: "float64",
typeDeserInfo: {
type: "primitive",
typeLength: 8,
deserialize: PRIMITIVE_DESERIALIZERS.get("float64"),
},
};

describe("DeserializationInfoCache", () => {
it("creates deserialization info for struct with primitive fields", () => {
const deserializationInfoCache = new DeserializationInfoCache([TIME_DEFINITION]);
const timeDeserInfo = deserializationInfoCache.getComplexDeserializationInfo(TIME_DEFINITION);
expect(timeDeserInfo).toMatchObject({
type: "struct",
fields: [
{
name: "sec",
type: "int32",
typeDeserInfo: {
type: "primitive",
typeLength: 4,
deserialize: PRIMITIVE_DESERIALIZERS.get("int32"),
},
},
{
name: "nanosec",
type: "uint32",
typeDeserInfo: {
type: "primitive",
typeLength: 4,
deserialize: PRIMITIVE_DESERIALIZERS.get("uint32"),
},
},
],
});
});

it("creates deserialization info for struct with complex fields", () => {
const deserializationInfoCache = new DeserializationInfoCache([
TRANSFORM_DEFINITION,
VECTOR_DEFINITION,
QUATERNION_DEFINITION,
]);
const timeDeserInfo =
deserializationInfoCache.getComplexDeserializationInfo(TRANSFORM_DEFINITION);
expect(timeDeserInfo).toMatchObject({
type: "struct",
fields: [
{
name: "translation",
type: "geometry_msgs::msg::Vector3",
typeDeserInfo: {
type: "struct",
fields: [
{
name: "x",
...FLOAT64_PRIMITIVE_DESER_INFO,
},
{
name: "y",
...FLOAT64_PRIMITIVE_DESER_INFO,
},
{
name: "z",
...FLOAT64_PRIMITIVE_DESER_INFO,
},
],
},
},
{
name: "rotation",
type: "geometry_msgs::msg::Quaternion",
typeDeserInfo: {
type: "struct",
fields: [
{
name: "x",
...FLOAT64_PRIMITIVE_DESER_INFO,
},
{
name: "y",
...FLOAT64_PRIMITIVE_DESER_INFO,
},
{
name: "z",
...FLOAT64_PRIMITIVE_DESER_INFO,
},
{
name: "w",
...FLOAT64_PRIMITIVE_DESER_INFO,
},
],
},
},
],
});
});

it("creates deserialization info for primitive field", () => {
const deserializationInfoCache = new DeserializationInfoCache([]);
const fieldDeserInfo = deserializationInfoCache.buildFieldDeserInfo({
name: "some_field_name",
type: "float64",
});
expect(fieldDeserInfo).toMatchObject({
name: "some_field_name",
...FLOAT64_PRIMITIVE_DESER_INFO,
});
});

it("creates deserialization info for primitive array field", () => {
const deserializationInfoCache = new DeserializationInfoCache([]);
const fieldDeserInfo = deserializationInfoCache.buildFieldDeserInfo({
name: "some_array_field",
type: "float64",
isArray: true,
});
expect(fieldDeserInfo).toMatchObject({
name: "some_array_field",
type: "float64",
isArray: true,
typeDeserInfo: {
type: "array-primitive",
typeLength: 8,
deserialize: PRIMITIVE_ARRAY_DESERIALIZERS.get("float64"),
},
});
});

it("creates deserialization info for complex field", () => {
const deserializationInfoCache = new DeserializationInfoCache([TIME_DEFINITION]);
const timeFieldDeserInfo = deserializationInfoCache.buildFieldDeserInfo({
isComplex: true,
name: "time",
type: "builtin_interfaces::Time",
});
expect(timeFieldDeserInfo).toMatchObject({
name: "time",
type: "builtin_interfaces::Time",
typeDeserInfo: {
type: "struct",
fields: [
{
name: "sec",
type: "int32",
typeDeserInfo: {
type: "primitive",
typeLength: 4,
deserialize: PRIMITIVE_DESERIALIZERS.get("int32"),
},
},
{
name: "nanosec",
type: "uint32",
typeDeserInfo: {
type: "primitive",
typeLength: 4,
deserialize: PRIMITIVE_DESERIALIZERS.get("uint32"),
},
},
],
},
});
});

it("creates deserialization info for complex array field", () => {
const deserializationInfoCache = new DeserializationInfoCache([VECTOR_DEFINITION]);
const timeFieldDeserInfo = deserializationInfoCache.buildFieldDeserInfo({
isComplex: true,
isArray: true,
name: "vectors",
type: "geometry_msgs::msg::Vector3",
});
expect(timeFieldDeserInfo).toMatchObject({
name: "vectors",
type: "geometry_msgs::msg::Vector3",
isArray: true,
typeDeserInfo: {
type: "struct",
fields: [
{
name: "x",
...FLOAT64_PRIMITIVE_DESER_INFO,
},
{
name: "y",
...FLOAT64_PRIMITIVE_DESER_INFO,
},
{
name: "z",
...FLOAT64_PRIMITIVE_DESER_INFO,
},
],
},
});
});

it("throws if required type definitions are not found", () => {
const deserializationInfoCache = new DeserializationInfoCache([TIME_DEFINITION]);
expect(() =>
deserializationInfoCache.getComplexDeserializationInfo(TRANSFORM_DEFINITION),
).toThrow();
expect(() =>
deserializationInfoCache.buildFieldDeserInfo({
name: "foo",
type: "some/unknown_type",
}),
).toThrow();
});
});
Loading

0 comments on commit 549c1f6

Please sign in to comment.