Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

perf(ngModel,ngForm): change ng-model/form controllers to use prototype methods #13286

Merged
merged 1 commit into from
Sep 14, 2016

Conversation

jbedard
Copy link
Contributor

@jbedard jbedard commented Nov 10, 2015

This makes the largetable-bp ng-model benchmarks 10-15% faster (down 90-100ms for me). The actual controller instantiation doesn't change too much but the overall numbers seem consistently faster, I assume all due to reducing memory usage / gc. Specifically on creation there is ~40% less memory GCed, on destruction there is about ~25% less.

Looking at the heap snapshot this is entirely from having less closures (~70k => 30k closures), other counts are all equal or very close.

Looking at the chrome timeline the number of GC events is about half and the time/gc amounts roughly match the benchpress measurements.

Worth the breaking change? The diff can probably be reduced or split into 2 commits if there's interest...

element.on('blur', function(ev) {
if (modelCtrl.$touched) return;

if ($rootScope.$$phase) {
scope.$evalAsync(modelCtrl.$setTouched);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is an example of the breaking change...

Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to add un-minified only checks to aid in the transition for these methods?

console.assert(this instanceof NgModelController, 'Some link to the breaking change message');

It's not worth these checks in the minified build imo since those should be the production assets, but it may be worth it in the debug/unminified builds.

@dcherman
Copy link
Contributor

I think if communicated well, this change is worth the break. For complex apps, the number of NgModelControllers being constructed and torn down on a page can represent a non-trivial amount of memory consumption and GC hits (as your tests show)

Out of curiosity, would this allow engines like v8 to better optimize this controller now that it uses a real prototype, or would have the similarly structured instances been backed by the same hidden class anyway?

@Narretz
Copy link
Contributor

Narretz commented Nov 11, 2015

I like it, now and then I wonder why it wasn't done like this before. I cannot estimate how many apps will be affected by the breaking change, but I think the perf gains are worth it.


if (control.$name) {
form[control.$name] = control;
this[control.$name] = control;
Copy link
Contributor

Choose a reason for hiding this comment

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

I know this is unrelated to this change, but this looks dangerous

@lgalfaso
Copy link
Contributor

Overall, I like this PR. It does need a better commit message and a proper breaking change description and migration

@dcherman
Copy link
Contributor

@lgalfaso Quick thought; right now, we have access to both this and ctrl in the controllers, so we can detect scenarios where ctrl !== this. Do you think it's worthwhile to add a warning in the unminified build so that if an unbound method was passed around, you get warnings?

That should make it easier to migrate to 1.6 with this breaking change since if you don't get these warnings, it's relatively certain that you won't run into problems.

@jbedard jbedard force-pushed the ngmodel-prototype branch 2 times, most recently from 40b47d3 to dd09c2c Compare April 13, 2016 04:05
@jbedard
Copy link
Contributor Author

jbedard commented Apr 20, 2016

I've rebased this...

@jbedard
Copy link
Contributor Author

jbedard commented Aug 27, 2016

Rebased again. Let me know if there's anything I can do to help get this into 1.6...

@Narretz
Copy link
Contributor

Narretz commented Aug 28, 2016

@jbedard I think we should merge it. You've never responded to #13286 (comment), do you know what @lgalfaso means by this?

@dcherman How can we log something in the unminfied build and have it removed in the minified buld? Can we use the Closure compiler for this?

@dcherman
Copy link
Contributor

dcherman commented Aug 28, 2016

@Narretz I'm not that familiar with what Closure Compiler offers in the way of options, but UglifyJS has a drop_console option that will remove console statements; that's how we remove chunks of code in our minified artifacts where I work. Since we're talking about simple function calls with known styles enforced by linting, it would also be relatively easy to write a regex to remove those invocations at build time if an existing option/plugin doesn't work for you.

FWIW, my idea needed to be landed before this change in order to be effective. You could stillassert something like this instanceof NgModelController if you really want to, but the original idea was to prevent breakage in the first place rather than react to it.

@jbedard
Copy link
Contributor Author

jbedard commented Aug 29, 2016

@Narretz he is referring to the fact that we are blindly setting this[name] where name is a value from an HTML attribute. What if name was "hasOwnProperty" or "$setValidity" etc. This is unrelated to this PR, but it is a good point.

@dcherman I'd rather not add debug-only code like that because many people who use their own (or no) minification won't remove it. An opt-in tool might work, but then no one will use it :|

@Narretz Narretz self-assigned this Sep 12, 2016
@Narretz Narretz modified the milestones: 1.6.0, 1.6.x (post 1.6.0) Sep 14, 2016
@Narretz
Copy link
Contributor

Narretz commented Sep 14, 2016

We've decided that we are not gonna log any warning messages in the console before this change. It's an old-fashioned BC and just have to deal with it (TM) during migration.

@Narretz
Copy link
Contributor

Narretz commented Sep 14, 2016

(I have rebase this locally, so no need to rebase it again. Just waiting if someone has any last thoughts on this)

BREAKING CHANGE:
The use of prototype methods instead of new methods per instance removes the ability to pass NgModelController and FormController methods without context.

For example `$scope.$watch("something", myNgModelCtrl.$render)` will no longer work because the `$render` method is passed without any context. This must now be replace with `$scope.$watch("something", function() { myNgModelCtrl.$render(); })` or possibly by using `Function.prototype.bind` or `angular.bind`.
@jbedard
Copy link
Contributor Author

jbedard commented Sep 14, 2016

I also just rebased this!

👍 for the BC without any extra warnings

@Narretz Narretz merged commit 9e24e77 into angular:master Sep 14, 2016
@dcherman
Copy link
Contributor

👏

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

Successfully merging this pull request may close these issues.

5 participants