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

$.observe/$.unobserve performance degradation over time #448

Closed
johan-ohrn opened this issue Jun 8, 2020 · 5 comments
Closed

$.observe/$.unobserve performance degradation over time #448

johan-ohrn opened this issue Jun 8, 2020 · 5 comments

Comments

@johan-ohrn
Copy link

johan-ohrn commented Jun 8, 2020

Please look at this fiddle

Notice how the observe/unobserve times increase for each iteration and then becomes lower again after ~15 or so iterations. The times are never as fast as the first couple of iterations.
I should say that this is not my actual data but it's a simple representation of what I'm doing.

Also the jquery version used doesn't seem to matter.

(code included below for future reference)

function arr() {
 var arr = [];

 for (var i = 0;i < 1000; i++) {
 	var o = {a:1, b:{c:11, d:{e:111}}};
 	arr.push(o);
 }
 return arr;
}
function fn() {
};

function test() {
 var a = arr();
 var start = performance.now();
 $.observe(a, "[].**", fn);
 var t1 = performance.now() - start;
 console.log("observe", t1);
 var start = performance.now();
 $.unobserve(a, "[].**", fn);
 var t1 = performance.now() - start;
 console.log("UNobserve", t1);
}
for(var i = 0; i < 25; i++){
	if (window.Stop) break;
	console.log("test "+i);
	test();
}
@johan-ohrn
Copy link
Author

I found one major performance improvement in the observable code. I'm not sure if it will fix the over time degradation though.
Anyway in jquery.observable.js:
line 793:

// cbBindings = cbBindingsStore[cbId] = cbBindingsStore[cbId] || {};
if (!cbBindingsStore[cbId]) {
  cbBindingsStore[cbId] = (function () {
    var target = {};
    Object.defineProperty(target, 'length', {
      enumerable: false,
      value: 0,
      writable: true
    });
    var p = new Proxy(target, {
      get: function (obj, prop) { return obj[prop] },
      set: function (obj, prop, value) { if (!obj.hasOwnProperty(prop)) obj.length++; obj[prop] = value; return true; },
      deleteProperty: function (obj, prop) { if (obj.hasOwnProperty(prop)) obj.length--; return delete obj[prop]; }
    });
    return p;
  })();
}
cbBindings = cbBindingsStore[cbId];

line 174:

//for (var cb in cbBindings) {
//  return;
//}
if (cbBindings.length > 0)
  return;

It turns out that when an object has many keys the for-in construct is painfully slow (at least in chrome). If you could use a length property like I did here the performance would become much better.
I didn't know where in the code to place the length++ and length-- statements so I used a Proxy as a proof-of-concept for your consideration. It doesn't work in IE.
In one of our (granted not very optimized page in terms of data size and number of objects) time dropped from about 5 seconds (after performance degradation) down to 700 ms.

If there are other parts of the jsviews code where you use the for-in construct to check if an object contains keys you might want to consider a dedicated length property.

@BorisMoore
Copy link
Owner

BorisMoore commented Jun 12, 2020

Thanks Johan, yes, I had been looking at the issue, and finally narrowed it down to that same line of code, (174) as the main culprit. (I found that just yesterday evening, so we were apparently working in parallel :) ...)

I will explore optimisations on that.

Also, for your "degradation over time" scenario, it seems not to be related to time, but to the fact of replacing the array a each time you run the test(), with a new set of array items. If you set a = arr(); at the outer scope, and don't replace it when you run test() then you can call test() successively many times, and the perf of each iteration is about the same. But I don't yet understand why replacing with a new a = arr() causes slower execution - or why it would cause line 174 to execute more slowly.

I'll investigate more, if I can...

@BorisMoore
Copy link
Owner

I followed your suggestion for a length property, but using a very 'low tech' approach that requires few lines of code, and works in IE too. It is here:

jsviews4.zip

It does indeed radically improve performance in your scenario of doing observeAll (or equivalent) on very big arrays.

I didn't manage to understand why replacing the arr() was causing slower execution of line 174 - seems like a quirk/bug/anomaly in Chrome JavaScript implementation. But with the modified observable code using cbBindings.len the slow down no longer happens....

Let me know if you have any thoughts or concerns....

@johan-ohrn
Copy link
Author

It works like a charm. Thanks!

BorisMoore added a commit to BorisMoore/jsviews.com that referenced this issue Jul 12, 2020
Feature improvements for JsRender on Node.js:

  (See issue BorisMoore/jsrender#357)

- File based templates for JsRender on Node.js now allow
  absolute paths

  See the updated documentation topic:
  https://www.jsviews.com/#node/filetmpls@htmlfile

- The renderFile() method for JsRender on Node.js
  now allows passing of context and helpers as well
  as data

  See the new documentation topic:
  https://www.jsviews.com/#node/filetmpls@renderfilehlelpers

Performance optimization changes:

- BorisMoore/jsviews#448
  $.observe/$.unobserve performance degradation over time

- BorisMoore/jsviews#447
  removeView performance

Several bug fixes, including:

- BorisMoore/jsviews#446
  Observable change error, when loading jquery.observable.js before
  jsrender.js, and using contextual parameters and sorted {{for}}

- BorisMoore/jsviews#440
  linked tag property changed handler called when it shouldn't

- BorisMoore/jsviews#445
  jsrender.min.js.map truncated

Additional small corrections or improvements to documentation
and some additional unit tests...
BorisMoore added a commit that referenced this issue Jul 12, 2020
Feature improvements for JsRender on Node.js:

  (See issue BorisMoore/jsrender#357)

- File based templates for JsRender on Node.js now allow
  absolute paths

  See the updated documentation topic:
  https://www.jsviews.com/#node/filetmpls@htmlfile

- The renderFile() method for JsRender on Node.js
  now allows passing of context and helpers as well
  as data

  See the new documentation topic:
  https://www.jsviews.com/#node/filetmpls@renderfilehlelpers

Performance optimization changes:

- #448
  $.observe/$.unobserve performance degradation over time

- #447
  removeView performance

Several bug fixes, including:

- #446
  Observable change error, when loading jquery.observable.js before
  jsrender.js, and using contextual parameters and sorted {{for}}

- #440
  linked tag property changed handler called when it shouldn't

- #445
  jsrender.min.js.map truncated

Additional small corrections or improvements to documentation
and some additional unit tests...
@BorisMoore
Copy link
Owner

This has been fixed in new release v1.0.7

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

2 participants