Skip to content

Make KD-tree decoder memory linear in the point dimension - #1241

Open
Filyus wants to merge 1 commit into
google:mainfrom
Filyus:kd-tree-decoder-undo-log
Open

Filyus wants to merge 1 commit into
google:mainfrom
Filyus:kd-tree-decoder-undo-log

Conversation

@Filyus

@Filyus Filyus commented Oct 4, 2026

Copy link
Copy Markdown

DynamicIntegerPointsKdTreeDecoder allocates memory quadratic in the point dimension, up front, in its constructor. The dimension is the total number of components over all attributes, and a stream sets it at five bytes per attribute. This change makes the decoder's memory linear in the dimension. The decoded output is unchanged.

Decoder memory by dimension:

dimension main, always allocated this change, at most
3 2.3 KB 1.6 KB
64 1 MB 33 KB
255 17 MB 133 KB
2,048 1.1 GB 1.1 MB
6,375 10 GB 3.3 MB

What this means for actual streams:

  • A 143-byte point cloud with 25 attributes of 255 components makes main commit about 10 GB before it reads any KD-tree data, and then fail on the missing data. With this change it fails without that allocation.
  • A valid 8,372-byte stream of three points with 2,048 components decodes with about 5 MB in 80 ms, against 1.1 GB and 1 to 1.9 s on main.
  • Ordinary point clouds decode as fast or faster. Best of 200 decodes, Release build with MSVC:
level components points main this change
0 3 50,000 1,722 us 1,705 us
2 3 50,000 1,967 us 1,921 us
6 3 50,000 2,032 us 1,894 us
4 6 20,000 1,007 us 948 us
2 64 2,000 487 us 338 us

How: the decoder kept a full copy of the node's base and levels for every level of the tree, which is 32 × dimension levels deep. A split changes only one base value and one level. The decoder now keeps a single base and levels vector, records the old values at each split, and restores them when the walk returns to a shallower node.

Verification:

  • The new test fails on main (AddressSanitizer runs out of memory in the constructor) and passes with this change. draco_tests passes under AddressSanitizer.
  • 1,200 generated point clouds over all compression levels and 1 to 100 components, each decoded as encoded and with 30 damaged copies, give the same result and the same attribute bytes on main and on this change. That probe is not part of the change.
  • Nine existing PointCloudKdTreeEncodingTest cases fail if the restore is broken, so the existing tests cover it.

DynamicIntegerPointsKdTreeDecoder allocates memory quadratic in the
point dimension, up front, in its constructor. The dimension is the
total number of components over all attributes, and a stream sets it at
five bytes per attribute. This change makes the decoder's memory linear
in the dimension. The decoded output is unchanged.

Decoder memory by dimension, main always allocated against this change
at most:

      3 components: 2.3 KB -> 1.6 KB
     64 components:   1 MB -> 33 KB
    255 components:  17 MB -> 133 KB
  2,048 components: 1.1 GB -> 1.1 MB
  6,375 components:  10 GB -> 3.3 MB

A 143-byte point cloud with 25 attributes of 255 components makes main
commit about 10 GB before it reads any KD-tree data, and then fail. With
this change it fails without that allocation. A valid 8,372-byte stream
of three points with 2,048 components decodes with about 5 MB in 80 ms,
against 1.1 GB and 1 to 1.9 s on main. Ordinary point clouds decode as
fast or faster, from 1% faster with 3 components to 30% faster with 64.

The decoder kept a full copy of the node's base and levels for every
level of the tree, which is 32 * dimension levels deep. A split changes
only one base value and one level. The decoder now keeps a single base
and levels vector, records the old values at each split, and restores
them when the walk returns to a shallower node.

This branch has not been deployed

No deployments
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.

1 participant