Skip to content

perf(cloudini_lib): faster single-field and lossless encoding/decoding - #167

Merged
facontidavide merged 4 commits into
facontidavide:mainfrom
jlblancoc:perf/o3-codec
Oct 25, 2026
Merged

facontidavide merged 4 commits into
facontidavide:mainfrom
jlblancoc:perf/o3-codec

Conversation

@jlblancoc

@jlblancoc jlblancoc commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Faster encoding and decoding of single-field clouds and lossless fields. The streams are byte-identical and the API is unchanged:

  1. Bulk decodePoints() for FieldDecoderCopy. Lossless fields, and float fields without a resolution, were decoded through the generic per-point loop, with a virtual decode() call and a libc memcpy() call per value; perf showed about 90% of the time there. A single-field cloud now takes one memcpy. Interleaved points take a strided copy with a compile-time size.
  2. Bulk encodePoints() for single-field clouds. I added a new virtual FieldEncoder::encodePoints(), whose default calls encode() once per point. The V4 codec uses it when a cloud has a single field. FieldEncoderCopy overrides it with a copy, and FieldEncoderFloat_Lossy with a loop that has no virtual call per point.
  3. -O3 in non-Debug builds (GCC and Clang). RelWithDebInfo, the default of ROS/colcon builds, and distribution packages compile with -O2. There, the lossy float decoder runs about 1.5x slower.

Motivation: in MRPT, each point field is a separate array, so each one is encoded as its own single-field Cloudini stream (in 64k-point chunks, in parallel). That is about 2.8-3.5x faster than raw serialization for large maps. Maps with many lossless fields (normals, timestamps...) spend most of their time in these two paths.

Measurements

Times per value (or per point, for x/y/z) on a 262k-point LiDAR-like cloud, use_threads=false. Both builds are RelWithDebInfo (the default in ROS/colcon), GCC 13, AMD Ryzen 7 2700X. "1.4.1" is current main.

Decoding

1.4.1 this PR
one lossless float field, ZSTD 7.5 ns 3.3 ns (2.3x), now bound by ZSTD
one lossless float field, LZ4 4.3 ns 0.25 ns (17x)
one lossless float field, no compression 4.5 ns 0.14 ns (32x)
x, y, z as 3 single-field lossy streams, ZSTD 21.3 ns 14.0 ns (1.5x)
x, y, z as 3 single-field lossy streams, LZ4 16.3 ns 10.3 ns (1.6x)
x, y, z interleaved (V6), ZSTD 15.2 ns 15.3 ns (unchanged)

Encoding

1.4.1 this PR
one lossless float field, ZSTD 8.5 ns 4.8 ns (1.8x)
one lossless float field, no compression 4.5 ns 0.43 ns (10x)
one lossy float field, ZSTD 10.2 ns 7.5 ns (1.4x)

Testing

  • New test FieldEncoders.SingleFieldBulkMatchesPerPoint. For contiguous and padded points, lossless and lossy, it checks that the bulk encoder writes the same bytes as encode() once per point. It also checks a whole-cloud round trip.
  • I compared the streams of single-field clouds across sizes (1 to 300k points), point steps, lossless/lossy and NONE/ZSTD against 1.4.1: they are byte-identical (9.6 MB in total).
  • All 58 tests pass.

RelWithDebInfo (the default of ROS/colcon builds) and distribution packages
build with -O2, where lossy float fields decode about 1.5x slower.
Lossless fields (and lossy float fields without a resolution) were decoded
through the generic per-point loop: a virtual decode() call and a libc
memcpy() call per value. A single-field cloud now takes one memcpy, and
interleaved ones a strided copy with a compile-time size: 37x faster without
compression, and 2.4x with ZSTD (now bound by ZSTD itself).
New FieldEncoder::encodePoints(), by default one encode() call per point, used
by the V4 codec when a cloud has a single field. FieldEncoderCopy overrides it
with one (or a strided) copy, FieldEncoderFloat_Lossy with a loop without a
virtual call per point. Streams are byte-identical. With ZSTD, a lossless
float field encodes 2x faster, and a lossy one 1.37x.
@jlblancoc jlblancoc changed the title perf(cloudini_lib): build the codec with -O3 in non-Debug builds perf(cloudini_lib): faster single-field and lossless encoding/decoding Oct 24, 2026
@jlblancoc
jlblancoc marked this pull request as ready for review October 24, 2026 12:22
@facontidavide

Copy link
Copy Markdown
Owner
image

@facontidavide
facontidavide merged commit 60ddb44 into facontidavide:main Oct 25, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants