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

refactor(*): replace HashMap with NgMap #15483

Closed
wants to merge 3 commits into from

Conversation

gkalpak
Copy link
Member

@gkalpak gkalpak commented Dec 9, 2016

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)
Refactoring.

What is the current behavior? (You can also link to an open issue here)
Same old, same old!

What is the new behavior (if this is a feature change)?
HashMap is replaced with NgMap, which for the time being points to NgMapShim, an API-compatible Map implementation (for the features Angular needs). This will allow us to easily switch to native implementations once they become more stable.

Does this PR introduce a breaking change?
No.

Please check if the PR fulfills these requirements

Other information:
See #13209 and #15114 for related discussions.

For the time being, we will be using `NgMap`, which is an API-compatible
implementation of native `Map` (for the features required in Angular). This will
make it easy to switch to using the native implementations, once they become
more stable.

Note:
At the momntm some native implementations are still buggy (often in subtle ways)
and can cause hard-to-debug failures.)
@gkalpak
Copy link
Member Author

gkalpak commented Dec 9, 2016

BTW, this is the same code as in #15114, minus #13209. The ES6Map part of #13209 is irrelevant now. The perf(copy) part of #13209 (which is still relevant) can be rebased and merged once this has been merged.

this.$get = [function() {
return HashMap;
return NgMap;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we feature detect native Map implementation and just use that if present? It should be significantly faster than this implementation since lookups are not O(n), and all modern browsers support it at this point. This fallback is really just for like...IE9 and IE10.

Copy link
Contributor

Choose a reason for hiding this comment

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

So I just saw the comments above because reading fail. What subtle bugs in native Maps exist such that we shouldn't use them for perf, other than IE11 return values not conforming to the spec? Can we add references to filed issues such that when these bugs are fixed, we know that it's safe to migrate?

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 is some discussion in #15114. It is not as much about specific bugs as it is about our lack of confidence that there aren't any (or that our tests would have caught them).

Copy link
Member Author

Choose a reason for hiding this comment

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

(For example, there was some Safari 8 bug (which I couldn't even figure out what it was) that caused our tests to fail, even if the required features were theoretically available.)

Copy link
Contributor

Choose a reason for hiding this comment

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

With that logic, the native Map will never be used as there is no concrete way to judge whether or not they're "ready" - that's why I was suggesting we put some kind of comment in here that "when these things are fixed, re-evaluate"

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 these things are fixed, re-evaluate"

Sounds great. If we only knew what these things were... 😞

@dcherman
Copy link
Contributor

dcherman commented Dec 9, 2016

So I know we were debating this in that other issue, but I still think an implementation closer to $$HashMap is more ideal with property lookups than using dual arrays for perf reasons.

It would not be a breaking change since due to how it worked, the old HashMap implementation could never support frozen objects anyway since it did a property assignment on them. This mainly affects IE9/IE10 though since in newer browsers, we should just use the native implementation.

@gkalpak
Copy link
Member Author

gkalpak commented Dec 9, 2016

According to my understanding, the discussion on #13209 was not conclusive on whether our implementation of HashMap is faster than our implementation of NgMap. Anyone feels like benchmarking? 😃

Furthermore, this benchmarking would only make sense for copy(), because based on #15114 (comment) other uses of NgMap don't seem to have a noticeable impact on performance. (And this PR does not affect copy(), so performance-wise it should make no difference overall.)

The main benefit of using NgMap imo is that it is easy to swap it for window.Map when we feel we're ready. Of course, we could change the underlying implementation of NgMapShim to use a HashMap if we find out it offers better performance, but keeping a window.Map-compatible interface is a good thing imo.

I suggest we merge this (which is just a refactoring - not a perf change) and debate performance on #13209 (which is a perf change).

@dcherman
Copy link
Contributor

dcherman commented Dec 9, 2016

JSPerf is down at the moment so I can't do any basic tests, but do you have any stats on what the average size of a Map is versus the max size? If N is low enough in your use cases, it's possible that it's fast enough, but I suspect that it'd breakdown with larger maps. Will try to test once I can make some benchmarks.

No matter what we do, I am 100% in agreement that we should refactor any APIs to use a Map compatible interface. As long as it's present in the injector as well rather than being a closure variable, applications could choose to override the private $$Map factory with the native one if they deem it important so long as they understand that they're tinkering with private details that are subject to change at any time.

@gkalpak
Copy link
Member Author

gkalpak commented Dec 9, 2016

As long as it's present in the injector as well rather than being a closure variable, applications could choose to override the private $$Map factory with the native one if they deem it important so long as they understand that they're tinkering with private details that are subject to change at any time.

I assume "people" is "you" 😛 The problem is that not all cases retrieve NgMap via DI. Off the top of my head, NgMap is used in the following places:

  • ngAnimate
  • ngOptions
  • createInjector()

(In the future, it may also be used by copy().)

ngAnimate already uses DI and ngOptions can be changed to use it too.
createInjector() obviously can't and I don't think copy() (which is probably the most critical performance-wise) can either.

@dcherman
Copy link
Contributor

dcherman commented Dec 9, 2016

I assume "people" is "you"

Yep. I tinker with a lot of internals for fun/performance sometimes, but always with the understanding that nothing I do is supported. Almost have an entire implementation of Observable bindings working that only participate in the digest cycle when necessary - including interpolation in templates and a new $ binding in components 😈

Anyway, I will try to run some basic perf tests when JSPerf is back up.

// //
// s6

// s1
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks unrelated?

Copy link
Member Author

Choose a reason for hiding this comment

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

Wrong indentation is always relevant! 😛

}
window.document.$$hashKey = null;
// These Nodes are persisted across tests.
// They used to be assigned a `$$hashKey` when animated, but not any more.
Copy link
Contributor

Choose a reason for hiding this comment

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

I find this could be more explicit. If you read this in a year , would you know hat this means?

Copy link
Member Author

Choose a reason for hiding this comment

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

You wouldn't? 😱
Updated the comment just in case 😛

@gkalpak
Copy link
Member Author

gkalpak commented Jan 24, 2017

PTAL

@Narretz
Copy link
Contributor

Narretz commented Jan 25, 2017

LGTM

@gkalpak gkalpak closed this in 641e13a Jan 25, 2017
gkalpak added a commit that referenced this pull request Jan 25, 2017
For the time being, we will be using `NgMap`, which is an API-compatible
implementation of native `Map` (for the features required in Angular). This will
make it easy to switch to using the native implementations, once they become
more stable.

Note:
At the moment some native implementations are still buggy (often in subtle ways)
and can cause hard-to-debug failures.)

Closes #15483
ellimist pushed a commit to ellimist/angular.js that referenced this pull request Mar 15, 2017
For the time being, we will be using `NgMap`, which is an API-compatible
implementation of native `Map` (for the features required in Angular). This will
make it easy to switch to using the native implementations, once they become
more stable.

Note:
At the moment some native implementations are still buggy (often in subtle ways)
and can cause hard-to-debug failures.)

Closes angular#15483
@gkalpak gkalpak deleted the refactor-HashMap-use-NgMap branch April 27, 2017 11:50
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.

None yet

5 participants