Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory overhead in scenes with many objects #1527

Open
donmccurdy opened this issue Oct 13, 2024 · 4 comments
Open

Memory overhead in scenes with many objects #1527

donmccurdy opened this issue Oct 13, 2024 · 4 comments

Comments

@donmccurdy
Copy link
Owner

donmccurdy commented Oct 13, 2024

tl;dr — Scenes with many (1M+) meshes/nodes/accessors cause higher than expected memory cost and potential OOM

While investigating a recent question on the Blender Stack Exchange, I noticed that a glTF file linked from the question1 caused an OOM when running gltf-transform inspect. The file contained 200 MB of uncompressed binary data, which isn't a problem, but more importantly some 500,000 distinct mesh primitives. If the Node.js memory limit is around 4GB, that implies a memory cost of something like 8kb per mesh primitive. I haven't measured the memory allocation causes in Node.js yet, but the number looked higher than expected.

I ran further tests by printing heap size incrementally during loading. Heap size went from nominal to 3.7GB after initializing accessors, and hit OOM while processing mesh primitives. Note, this is not the binary vertex data, which has very predictable binary size, but the property graph organizing the scene/nodes/meshes/accessors on top of that data. On a hunch, I next tried to disable all event dispatchers and event listeners (managed by property-graph). This reduced heap size after initializing accessors to 2.4GB, and the scene loaded. Without events, some graph edges are never connected to the Root, so this leaves us with two plausible causes:

  1. The events/listeners (and associated closures) cost more memory than expected
  2. The memory cost is due to graph size, not events/listeners, and disabling the events reduced memory because another 1.5–2M graph edges were never connected

My feeling is that (1) is easier to fix, but (2) is more likely, and some more investigation will be needed to determine if one of these, or something else, is the cause.

Footnotes

  1. cad_machine.gltf, 7M vertices, 0.5M mesh primitives, 1.3M accessors

@donmccurdy
Copy link
Owner Author

donmccurdy commented Oct 22, 2024

Results from memory profiling in Chrome devtools:

  1. Objects, 20%: 8.2M objects, all reported appear to be primitive objects. Mostly, just seeing the $attributes for Property instances here. A typical accessor holds about 120 bytes in the $attributes entry. I see a non-trivial number of empty objects: .extras={}, .extensions={}, .listeners={} or .listeners={dispose}, etc. These appear to cost a flat 56 bytes, both {} and {dispose}.
  2. Sets, 16%: 2.7M sets, one with a shallow size (presumably part of the graph itself). The rest are individually trivial. But there are many of them, at about 152 bytes each, which still adds up to 400 MB.
  3. Arrays, 10%: 1.5M arrays are allocated, none exceedingly large, mostly in the property-graph module.
  4. Functions, 5%: 2.7M functions, in total 5% of retained size. The vast majority of callbacks were registered in the property-graph module.
  5. Accessor, 4%: 1.4m accessors
  6. GraphEdge, 4%: 1.4m graph edges
  7. RefMap, 2%: 1.4M refmaps

I'm surprised that instances of common classes like Accessor and GraphEdge were not more significant memory consumers.

@donmccurdy
Copy link
Owner Author

donmccurdy commented Oct 22, 2024

Ideas possibly worth exploring to reduce memory usage:

@donmccurdy
Copy link
Owner Author

donmccurdy commented Oct 25, 2024

I think we can safely remove events associated with GraphEdge, i.e. the references between properties. See:

The change improves the 'dispose' benchmark by 50%...

dispose
v3.10.1             ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀     0.5123ms
v4.0.0              ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀         0.4611ms
v4.0.9              ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀       0.4837ms
dev                 ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀                          0.25ms

... the benchmark runs an arbitrary number of dispose() operations on an arbitrarily-large Document, so the number itself is not meaningful, but lower is better. The change also significantly reduces memory overhead while loading the cad_machine.gltf asset discussed here.

@donmccurdy
Copy link
Owner Author

Somewhat relevant:

https://github.com/microsoft/deoptexplorer-vscode

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant