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

[perf] disabling _populateCache() in loader in favor of an on-demand process to create internal module info based on the raw meta when the module is needed #1581

Merged
merged 21 commits into from
Mar 28, 2014

Conversation

caridy
Copy link
Member

@caridy caridy commented Jan 24, 2014

Initial analysis:

For a very basic page with all necessary modules in the page upfront, and a YUI().use(‘json-stringify’) call, will run under 20ms in chrome/desktop, but it takes more than 100ms in safari iPad3.

For a more complex example, adding more modules (app level modules), we can clearly see more degradation of the initialization, even though none of those modules are "used". Here is an example using modules from screen.yahoo.com (almost 400 new modules + the 407 modules from yui core):

http://jsbin.com/iPOriSAS/1/edit

For what we can see in the profiling, _populateCache(), which is executed once per Y instance, it will do a one time operation to digest all the raw metadata from YUI.Env.modules, passing them thru addModule() method that will do a basic expansion of the meta without doing the recursive computation to expand the dependencies, that happens on demand.

By using the loader built of this branch, we seed a good deal of improvement, and overall a constant time to use despited the number of extra modules added thru the metas, which is exactly what we are looking for.

Changes:

Loader keeps a member called moduleInfo, which is a hash with the internal meta for every module. This structure is used within the loader and from yui.js as well. This PR is proposing a shim on top of that structure to se can populate the cache for each individual module from the raw meta structure YUI.Env.modules when we needed. This will help to not have to walk thousand of modules as part of the initialization of the first YUI instance.

A quirk on this new way of initialization is that conditional modules are not requested by any other module in the require chain, which means they will have to be analyzed before applying any operation to compute dependencies, for that, we are just warming up the conditional cache (which is normally 16 entries for yui core), instead of just initializing all core modules (400+).

Performance tests

command: yb src/loader/tests/performance/loader-tests.js --phantom --ref v3.14.1 --ref v3.15.0

loader resolve core modules

┌───────────┬───────────────────────────┬───────────────────────────────────┬───────────────────────────┐
│           │  Safari (7.0.1) / Mac OS  │  Chrome (33.0.1750.117) / Mac OS  │  Firefox (27.0) / Mac OS  │
├───────────┼───────────────────────────┼───────────────────────────────────┼───────────────────────────┤
│  v3.14.1  │  44.788  ±3.7%            │  44.546  ±3.8%                    │  21.736  ±4.6%            │
│  Working  │  189.414  ±4.4%  +323%    │  127.738  ±8.9%  +187%            │  86.822  ±3.8%  +299%     │
│  v3.15.0  │  157.768  ±3.9%  +252%    │  109.642  ±4.7%  +146%            │  80.497  ±3.9%  +270%     │
└───────────┴───────────────────────────┴───────────────────────────────────┴───────────────────────────┘

loader resolve application modules

┌───────────┬───────────────────────────┬───────────────────────────────────┬───────────────────────────┐
│           │  Safari (7.0.1) / Mac OS  │  Chrome (33.0.1750.117) / Mac OS  │  Firefox (27.0) / Mac OS  │
├───────────┼───────────────────────────┼───────────────────────────────────┼───────────────────────────┤
│  v3.14.1  │  595.568  ±5.7%           │  245.833  ±7.7%                   │  231.745  ±5.0%           │
│  Working  │  6.721k  ±3.3%  +1029%    │  3.450k  ±7.1%  +1304%            │  2.500k  ±4.3%  +979%     │
│  v3.15.0  │  696.895  ±3.0%  +17%     │  279.307  ±4.9%  +14%             │  260.851  ±3.8%  +13%     │
└───────────┴───────────────────────────┴───────────────────────────────────┴───────────────────────────┘

caculate dependecies

┌───────────┬───────────────────────────┬───────────────────────────────────┬───────────────────────────┐
│           │  Safari (7.0.1) / Mac OS  │  Chrome (33.0.1750.117) / Mac OS  │  Firefox (27.0) / Mac OS  │
├───────────┼───────────────────────────┼───────────────────────────────────┼───────────────────────────┤
│  v3.14.1  │  87.696  ±3.8%            │  78.881  ±4.2%                    │  44.247  ±4.4%            │
│  Working  │  306.211  ±3.0%  +249%    │  217.144  ±8.9%  +175%            │  154.839  ±3.1%  +250%    │
│  v3.15.0  │  224.967  ±2.7%  +157%    │  132.550  ±7.0%  +68%             │  110.696  ±3.9%  +150%    │
└───────────┴───────────────────────────┴───────────────────────────────────┴───────────────────────────┘

loader constructor with global cache

┌───────────┬───────────────────────────┬───────────────────────────────────┬───────────────────────────┐
│           │  Safari (7.0.1) / Mac OS  │  Chrome (33.0.1750.117) / Mac OS  │  Firefox (27.0) / Mac OS  │
├───────────┼───────────────────────────┼───────────────────────────────────┼───────────────────────────┤
│  v3.14.1  │  1.159k  ±4.6%            │  402.577  ±4.5%                   │  419.501  ±4.0%           │
│  Working  │  35.813k  ±3.5%  +2990%   │  9.923k  ±7.8%  +2365%            │  12.131k  ±3.6%  +2792%   │
│  v3.15.0  │  1.203k  ±3.0%  +4%       │  406.346  ±4.1%  +1%              │  435.124  ±3.6%  +4%      │
└───────────┴───────────────────────────┴───────────────────────────────────┴───────────────────────────┘

loader constructor without cache

┌───────────┬───────────────────────────┬───────────────────────────────────┬───────────────────────────┐
│           │  Safari (7.0.1) / Mac OS  │  Chrome (33.0.1750.117) / Mac OS  │  Firefox (27.0) / Mac OS  │
├───────────┼───────────────────────────┼───────────────────────────────────┼───────────────────────────┤
│  v3.14.1  │  143.610  ±8.3%           │  127.687  ±5.7%                   │  95.255  ±7.2%            │
│  Working  │  4.830k  ±2.9%  +3263%    │  3.451k  ±12.1%  +2603%           │  2.972k  ±3.7%  +3020%    │
│  v3.15.0  │  138.078  ±8.1%  -4%      │  135.419  ±6.8%  +6%              │  91.891  ±5.6%  -4%       │
└───────────┴───────────────────────────┴───────────────────────────────────┴───────────────────────────┘

@caridy
Copy link
Member Author

caridy commented Jan 30, 2014

This is proven to be worth it, bumping the priority of this one. A clean up is needed! /cc @ekashida

* @param {string} name of the module
* @private
*/
_getModuleInfo: function(name) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the accessor to the public moduleInfo property be private?

Copy link
Member Author

Choose a reason for hiding this comment

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

good question. It seems that moduleInfo was public, but we were not expecting people to use it. That sounds off! http://yuilibrary.com/yui/docs/api/classes/Loader.html#property_moduleInfo

We could make getModuleInfo public, and make moduleInfo private (at least in the docs).

var rawMetaModules = META.modules,
globalConditions = GLOBAL_ENV._conditions,
globalRenderedMods = GLOBAL_ENV._renderedMods,
internal = this._internal;
Copy link
Contributor

Choose a reason for hiding this comment

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

Need to change this semicolon to a comma.

@ezequiel ezequiel self-assigned this Mar 4, 2014
@ezequiel ezequiel added this to the Sprint 13 milestone Mar 4, 2014
@ezequiel ezequiel added the Loader label Mar 8, 2014
@ezequiel ezequiel removed their assignment Mar 12, 2014
@caridy
Copy link
Member Author

caridy commented Mar 14, 2014

This PR is ready for a formal review.

for (i in cache) {
if (cache.hasOwnProperty(i)) {
self.moduleInfo[i] = Y.merge(cache[i]);
_getModuleInfo: function(name) {
Copy link
Member Author

Choose a reason for hiding this comment

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

This is the new accessor for module info, although, in some places we are intentionally using the internal hash this.moduleInfo to avoid the penalty of the function call, even though there is a short circuit in this method for that case. Ideally, we can test this and simply drop the property in favor of the accessor, or we can replace the property with a define property (when that feature is available), and that will help us to control this better.

@ezequiel
Copy link
Contributor

I think this pull request inadvertently fixes #768 and #769.

if (cache.hasOwnProperty(i)) {
self.moduleInfo[i] = Y.merge(cache[i]);
if (this.moduleInfo[name]) {
return this.moduleInfo[name];
Copy link
Member

Choose a reason for hiding this comment

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

You could optimize this lookup by not doing it twice, but once and bind it to a local var.

@ericf
Copy link
Member

ericf commented Mar 21, 2014

This should be in a 3.x release because it changes the public moduleInfo. We should have a way for people to opt back into eagerly populating moduleInfo.

defaults = META.modules,
cache = GLOBAL_ENV._renderedMods,
i;
getModuleInfo: function(name) {
Copy link
Member

Choose a reason for hiding this comment

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

Is this staying private even though the method name does not start with an underscore?

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 think it is ok to make it public, as the accessor for module info.

Copy link
Member

Choose a reason for hiding this comment

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

The API doc comment should be updated then.

@ericf
Copy link
Member

ericf commented Mar 26, 2014

The updates look good. I added a comment about whether getModuleInfo() is meant to be public or private.

delete mod.langCache;
delete mod.skinCache;
mod.langCache = undefined;
mod.skinCache = undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why? Setting a property to undefined is different than deleteing it.

Copy link
Member Author

Choose a reason for hiding this comment

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

most js engines will go crazy about deleting a member of an object, part of the micro optimizations they do thru the synthetic classes. Deleting a member of a module meta object will simply throw the class away and will require extra work to access any member.

This is obviously not relevant for dictionaries/hashes, which is not the case here.

@ezequiel
Copy link
Contributor

👍

@ericf
Copy link
Member

ericf commented Mar 28, 2014

What do the coverage numbers look like for the code that was changed in this diff? Since this is loader I want us to be extra cautious. We might want to add some tests, especially for the new getModuleInfo() method.

Also we need may need User Guide updates.

@ericf
Copy link
Member

ericf commented Mar 28, 2014

👍

@caridy caridy merged commit 0829ca1 into yui:dev-3.x Mar 28, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants