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

reduce memory allocation in component update core codepath #3772

Merged
merged 6 commits into from
Oct 4, 2018

Conversation

ngokevin
Copy link
Member

@ngokevin ngokevin commented Sep 25, 2018

Description:

Rather than create a new object each time to create a new oldData, just have two objects representing oldData and data. Also when updating, rather than create a new data object, have a newData object that is used for holding the new data which then gets copied into data. So in total, only use three objects: oldData, data, newData.

Changes proposed:

  • Adapt style-attr implementation to be able to pass in an object for reuse instead of creating a new one each time. Also remove lots of callbacks, Object.keys() array creations from their code. Also tweaked to do prettier stringifies. We manage this forked dependency now.
  • Use object pooling in core component code to reduce object allocations. Recycle them when component is removed.
      1. Remove object allocations on each update for deep cloning data to oldData.
      1. Remove object allocation on initialization for initial oldData.
      1. Remove object allocation on each update during buildData for default value copying.
      1. Remove object alloaction during buildData, was using Object.keys.
  • Add some helper properties to component (isSingleProp, isSinglePropObject, isObjectBased)
  • Update our styleParser utils a bit to not allocate object + array + use callbacks to convert keys to camelCase, and to cache regexes.

Updated to not delete the keys when recycling an object, but just set every key to undefined. This means object pooling best used when objects share a common schema, in case we iterate over keys. So I create different object pools for each type of component, since their object keys will be the same. I also update schema parser to ignore undefined keys.

Before

screen shot 2017-10-03 at 7 16 43 pm

After

Improvements: See the updateProperties and updateCachedAttrValue no longer on the list of frequent memory allocators.

screen shot 2017-10-03 at 7 17 57 pm

  • Still need to polish/comment a bit more (and try to remove the clones for default values). But up and working again.
  • Tests

@ngokevin ngokevin added this to the 0.9.0 milestone Sep 26, 2018
@ngokevin ngokevin force-pushed the objectpool2 branch 2 times, most recently from 9ae6222 to 8af4a5b Compare September 26, 2018 10:30
@ngokevin ngokevin changed the title [WIP] object pooling in component core, optimize style parser to reuse object reduce memory allocation in component update codepath Sep 26, 2018
@ngokevin ngokevin changed the title reduce memory allocation in component update codepath reduce memory allocation in component update core codepath Sep 26, 2018
@ngokevin
Copy link
Member Author

ngokevin commented Sep 26, 2018

Ready to go. Did another test with the /performance/animation-set-attribute stress test to measure memory. These recordings were taken over 4 seconds. We save the following allocation calls:

  • buildData (component) (~400KB)
  • getKeyValueChunks (style-parser) (~160KB)
  • cloneData (~80KB)
  • updateProperties (~28KB)

With the particular stress test, it does lose out on ~5FPS (20FPS vs 15FPS), but it's also an unrealistic example since we wouldn't use setAttributes that hard and often for animations. Dodging GC is more important because GC will lose you entire frames.

We could perhaps have an option for components to not do any diffing, cloning, comparing, and just always run the update method no matter what, and let the component sort out data changes. We are probably losing a lot of time making sure redundant calls don't get processed.

Before

before

After

after

@arpu
Copy link
Contributor

arpu commented Sep 30, 2018

@ngokevin i would like test this change, how do you generate the memory statistics?

@@ -79,6 +79,11 @@ module.exports.Component = registerComponent('camera', {
// Camera disabled. Set camera to another camera.
system.disableSpectatorCamera();
}

this.savedPose = {
position: el.getAttribute('position'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have to allocate new objects here. You will be holding a reference to the entity shared objects that will change.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just removed it, it wasn't used.

@ngokevin
Copy link
Member Author

browser memory tools

@dmarcos
Copy link
Member

dmarcos commented Oct 3, 2018

It would be nicer if this PR could
be broken in smaller PRs. It touches a lot
of files and it’s hard to follow and see implications

@@ -21,12 +21,16 @@ module.exports.Component = registerComponent('obj-model', {
update: function () {
var data = this.data;
if (!data.obj) { return; }
this.remove();
this.resetMesh();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to the purpose of the PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixes a test

var shader = newShader || currentShader;
var schema = shaders[shader] && shaders[shader].schema;
var currentShader;
var newShader;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated to PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixes test, checks for data / oldData existence

*/
updateSchema: function (data) {
var currentGeometryType = this.oldData && this.oldData.primitive;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated to PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it checks for oldData rather than data

@dmarcos
Copy link
Member

dmarcos commented Oct 3, 2018

styleParser.js and component.js changes could be separate PRs

@ngokevin
Copy link
Member Author

ngokevin commented Oct 3, 2018

It's hard and effort to break up. They both relate to the object pooling and both tie together. There's only two major changes:

  • remove allocations from style parser by passing an object in (managed with object pool)
  • remove allocations from component.js by removing the cloning (managed with object pool)

I detail listed all the changes in the original description. It touches lots of files because it's a change deep in the core of A-Frame.

@dmarcos dmarcos merged commit 6deaea0 into aframevr:master Oct 4, 2018
@arpu
Copy link
Contributor

arpu commented Oct 6, 2018

this breaks my little app with

body.js:122 Uncaught TypeError: Cannot read property 'toUpperCase' of undefined at initBody (body.js:122) at HTMLElement. (a-node.js:300) at a-node.js:131 initBody @ body.js:122 emit @ a-node.js:300 (anonymous) @ a-node.js:131 Promise.then (async) load @ a-node.js:128 load @ a-entity.js:255 (anonymous) @ a-scene.js:591 setTimeout (async) play @ a-scene.js:590 attachedCallbackPostCamera @ a-scene.js:128 (anonymous) @ a-scene.js:102 emit @ a-node.js:300 checkUserCamera @ camera.js:98 (anonymous) @ camera.js:58 emit @ a-node.js:300 setObject3D @ a-entity.js:166 init @ camera.js:28 updateProperties @ component.js:315 updateComponent @ a-entity.js:489 updateComponents @ a-entity.js:463 (anonymous) @ a-entity.js:259 (anonymous) @ a-node.js:130 Promise.then (async) load @ a-node.js:128 load @ a-entity.js:255 (anonymous) @ a-entity.js:84 emit @ a-node.js:300 (anonymous) @ a-node.js:131 Promise.then (async) load @ a-node.js:128 load @ a-assets.js:83 (anonymous) @ a-assets.js:70 setTimeout (async) value @ a-assets.js:66

https://github.com/donmccurdy/aframe-physics-system/blob/master/src/components/body/body.js#L64

@ngokevin
Copy link
Member Author

ngokevin commented Oct 6, 2018

can you log this.data, and more information if you're updating it, initializing it...a live example would be great

@arpu
Copy link
Contributor

arpu commented Oct 6, 2018

here is a live example https://codesandbox.io/s/5k1wxylxkx

@ngokevin
Copy link
Member Author

ngokevin commented Oct 7, 2018

Thanks, possible to have one with the physics as an unminified project file so I can poke around in it?

@arpu
Copy link
Contributor

arpu commented Oct 8, 2018

https://codesandbox.io/s/vv6jm1l06l

with unminified physics

@arpu
Copy link
Contributor

arpu commented Oct 12, 2018

@ngokevin should i open a new bugreport?

@ngokevin
Copy link
Member Author

thanks sure

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

Successfully merging this pull request may close these issues.

3 participants