Skip to content

Conversation

@dmarcos
Copy link
Member

@dmarcos dmarcos commented May 19, 2017

I'm trying to find ways to optimize setAttribute / getAttribute calls specially when they're called once per frame like for example in a component's tick method.

My first attempt is to disable type checking when setAttribute is called twice with the same object. Types are checked the first time and then the object is considered trusted. This would allow to construct your tick method in the following way:

  tick: (function () {
      var newRotation = {};
      return function () {
      var el = this.el;
      var currentRotation = el.getAttribute('rotation');
      newRotation .x = currentRotation.x + 0.1;
      newRotation.y = currentRotation.y + 0.1;
      newRotation.z = currentRotation.z + 0.1;
      el.setAttribute('rotation', newRotation );
    }
  })()

This aligns with the good practice of avoiding object allocations inside your render loop. This optimization yields a 30%-40% percent FPS improvement with respect to master when rotating 2500 cubes using a tick like the above one.

@ngokevin
Copy link
Member

ngokevin commented May 19, 2017

Doesn't the closure pattern cause objects to get shared across different instances of the same type of component? We've ran into issues with that before. Sometimes I wish for some of the advantages of closure-based component definitions.

* @return {object} The component data
*/
function buildData (el, name, attrName, schema, elData, silent) {
function buildData (el, name, attrName, schema, elData, oldElData, silent, skipTypeChecking) {
Copy link
Member

@ngokevin ngokevin May 19, 2017

Choose a reason for hiding this comment

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

We should just plop this function back into the component prototype as a method now, too many arguments.

@dmarcos
Copy link
Member Author

dmarcos commented May 19, 2017

You should not use the closure pattern to store state of the component. In this case is just an auxiliary variable to make a temporary computation so it's fine.

@fernandojsg
Copy link
Member

30-40% improvements here in the 5k cubes scene

@dmarcos dmarcos force-pushed the cachObjectSetAttribute branch 3 times, most recently from c9c8cf3 to c0f190e Compare May 19, 2017 21:41
@dmarcos dmarcos changed the title Skip type checking if an object is passed through setAttribute twice [WIP] May 19, 2017
@@ -0,0 +1,46 @@
var URL_PARAMS = getUrlParams();
Copy link
Member

Choose a reason for hiding this comment

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

Might be nice to encapsulate this in a main component <a-scene main>

*/
updateComponent: {
value: function (attr, attrValue) {
value: function (attr, attrValue, clobber) {
Copy link
Member

Choose a reason for hiding this comment

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

I'd doc-string this to note .setAttribute passes this in

var el = this.el;
var isSinglePropSchema = isSingleProp(this.schema);
var oldData = extendProperties({}, this.data, isSinglePropSchema);
var skipTypeChecking = typeof this.previousAttrValue === 'object' && attrValue === this.previousAttrValue;
Copy link
Member

Choose a reason for hiding this comment

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

I'd comment the conditions of type checking (If the passed data is an object and the object has not changed)

var isSinglePropSchema = isSingleProp(this.schema);
var oldData = extendProperties({}, this.data, isSinglePropSchema);
var skipTypeChecking = typeof this.previousAttrValue === 'object' && attrValue === this.previousAttrValue;
var currentAttrValue = !clobber && this.attrValue;
Copy link
Member

Choose a reason for hiding this comment

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

What's !clobber && this.attrValue mean? Can you add a comment

var oldData = extendProperties({}, this.data, isSinglePropSchema);
var skipTypeChecking = typeof this.previousAttrValue === 'object' && attrValue === this.previousAttrValue;
var currentAttrValue = !clobber && this.attrValue;
this.previousAttrValue = attrValue;
Copy link
Member

Choose a reason for hiding this comment

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

We can define this in the constructor as well this.previousAttrValue = undefined

this.previousAttrValue = attrValue;

if (attrValue !== undefined) { this.updateCachedAttrValue(attrValue); }
if (!el.hasLoaded) {
Copy link
Member

Choose a reason for hiding this comment

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

Can you comment this clause?

this.updateSchema(this.buildData(attrValue, currentAttrValue, true));
}
this.data = buildData(el, this.name, this.attrName, this.schema, this.attrValue);
this.data = this.buildData(attrValue, currentAttrValue, false, skipTypeChecking);
Copy link
Member

Choose a reason for hiding this comment

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

We could have buildData's third argument be an options object so we can be explicit vs passing in bools

buildData(attrValue, currentAttrValue, {skipTypeChecking: true, silent: false})
Copy link
Member Author

Choose a reason for hiding this comment

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

it's maybe premature if we only have two flags? It's also not a pattern we have in other parts of the code

Copy link
Member

Choose a reason for hiding this comment

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

Not concerned about the number of flags, but being explicit, because calling a function build(value, oldValue, true, false), hard to know what true/false does in that order.

But maybe not so we don't create another object.

Copy link
Member Author

Choose a reason for hiding this comment

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

buildData is not part of the public API and only used in two places in the whole code base so the chances of misuse are small. And also what you said. It's called only in two places but invoked on every component update so better to avoid new object instantations.

this.data = buildData(el, this.name, this.attrName, this.schema, this.attrValue);
this.data = this.buildData(attrValue, currentAttrValue, false, skipTypeChecking);

if (attrValue !== undefined) { this.updateCachedAttrValue(attrValue); }
Copy link
Member

Choose a reason for hiding this comment

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

Curious when would it be undefined?

Copy link
Member Author

Choose a reason for hiding this comment

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

when the component is defined via mixins for example. there's not an attrValue on the entity

* @param {boolean} skipTypeChecking - Skip type checking and cohercion.
* @return {object} The component data
*/
buildData: function (elData, oldElData, silent, skipTypeChecking) {
Copy link
Member

Choose a reason for hiding this comment

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

nit: half the code calls it elData and half calls it attrValue, should we normalize?

el.updateComponentProperty(componentName, propName, propertyValue);
}

/**
Copy link
Member

Choose a reason for hiding this comment

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

The logic here and below are handled by which pieces now?

Copy link
Member Author

Choose a reason for hiding this comment

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

There was a lot of redundancy in that logic. The flag clobber basically replaces all of it

@dmarcos dmarcos force-pushed the cachObjectSetAttribute branch 3 times, most recently from a8f06b3 to 25ca595 Compare May 21, 2017 21:13
@dmarcos dmarcos force-pushed the cachObjectSetAttribute branch from 25ca595 to 1a45e04 Compare May 21, 2017 21:18
@ngokevin ngokevin merged commit 9eacbb6 into aframevr:master May 23, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

3 participants