Skip to content

Conversation

@mraleph
Copy link
Member

@mraleph mraleph commented Jul 4, 2022

Instead of using a HashMap and copying it down the tree
which leads to quadratic time and space complexity
use a persistent data structure which can amortise
the cost by sharing parts of the structure.

The data shows HAMT based PersistentHashMap to be
5-10x faster for building _inheritedWidgets and
considerably more space efficient (e.g. bringing
amount of memory allocated when constructing
_inheritedWidgets in a tree with 150 InheritedWidget
down to 70Kb from 970Kb).

PersistentHashMap is slower than HashMap for
access: 2-3x in relative terms, but in absolute
terms we are only talking about ~0.2ns slow down
per access and various app benchmarks we run have
have not revealed any significant regressions.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Instead of using a HashMap and copying it down the tree
which leads to quadratic time and space complexity
use a persistent data structure which can amortize
the cost by sharing parts of the structure.

The data shows HAMT based PersistentHashMap to be
5-10x faster for building _inheritedWidgets and
considerably more space effecient (e.g. bringing
amount of memory allocated when constructing
_inheritedWidgets in a tree with 150 InheritedWidget
down to 70Kb from 970Kb).

PersistentHashMap is slower than HashMap for
access: 2-3x in relative terms, but in absolute
terms we are only talking about ~0.2ns slow down
per access and various app benchmarks we run have
have not revealed any significant regressions.
@flutter-dashboard flutter-dashboard bot added the framework flutter/packages/flutter repository. See also f: labels. label Jul 4, 2022
@mraleph mraleph requested review from dnfield and gaaclarke July 4, 2022 14:01
@dnfield dnfield requested a review from goderbauer July 6, 2022 04:55
@mraleph
Copy link
Member Author

mraleph commented Jul 6, 2022

I addressed @dnfield's comments and did some dart2js directed optimizations, mainly moved away from implicit o as X casts to implicit T X x = o as dynamic. This allows dart2js to omit these casts, which are otherwise very ineffecient. VM would still perform casting.

This brings dart2js O4 (peak) performance almost on par with AOT on this code.

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

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

(not a full review yet, will come back to this)

// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/// A collection of key/value pairs, from which you can retrieve a value
Copy link
Member

Choose a reason for hiding this comment

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

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 sentence is copied from Map dartdoc. I am open to suggestions on how to rewrite it better.

Copy link
Member

Choose a reason for hiding this comment

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

"A collection of key/value pairs, from which a value can be retrieved using its associated key."?

///
/// Note: unlike [Map] this class does not support `null` as a key value and
/// implements only a functionality needed for a specific use case at the
/// core of the framework.
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 still general purpose enough to expose as public API?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, I would really like to avoid that, but I could not figure out how. It does not seem to be possible. Any suggestions?

Originally I just wanted to reference it as package:flutter/src/foundation/persistent_hash_map.dart making it clear that it is not a public API - but there is a lint check that prevents this sort of stuff.

Alternative is to put it in widgets/ and use just there - but it felt misplaced there (and I am not sure that tests would be able to import package:flutter/src/widgets/persistent_hash_map.dart

Any idea on how to approach this?

Copy link
Member

Choose a reason for hiding this comment

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

The current approach is probably the best one to allow for testability.

@mraleph mraleph requested review from dnfield and goderbauer July 12, 2022 07:51
Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

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

The implementation looks good to me. I do have a question about the benchmark data: Are those just coming from artificial benchmarks or has it been verified with some "real-world" app (e.g. OpenPay) that this actually gives us a real and measurable performance benefit and that the slower lookups don't actually end up hurting overall performance?

// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/// A collection of key/value pairs, from which you can retrieve a value
Copy link
Member

Choose a reason for hiding this comment

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

"A collection of key/value pairs, from which a value can be retrieved using its associated key."?

///
/// Note: unlike [Map] this class does not support `null` as a key value and
/// implements only a functionality needed for a specific use case at the
/// core of the framework.
Copy link
Member

Choose a reason for hiding this comment

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

The current approach is probably the best one to allow for testability.

/// * [Clojure's `PersistentHashMap`](https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentHashMap.java).
///
class PersistentHashMap<K extends Object, V> {
/// An an empty hash map.
Copy link
Member

Choose a reason for hiding this comment

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

nit: replace the first "An" with "Creates"?

Comment on lines 101 to 102
final _TrieNode newNode = node.put(
bitIndex + _TrieNode.hashBitsPerLevel, key, keyHash, value);
Copy link
Member

Choose a reason for hiding this comment

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

nit: (here and elsewhere): Flutter style wants the line of the opening and closing parenthesis to have to same indentation. Either put this all on one line or format it with a trailing comma (each param on a separate line followed by the parenthesis on the next line.

@mraleph
Copy link
Member Author

mraleph commented Jul 15, 2022

The implementation looks good to me. I do have a question about the benchmark data: Are those just coming from artificial benchmarks or has it been verified with some "real-world" app (e.g. OpenPay) that this actually gives us a real and measurable performance benefit and that the slower lookups don't actually end up hurting overall performance?

You can see the data I have in go/flutter-persistent-hash-map-for-inherited-widgets - its a mix of real world data and microbenchmarks.

(heads up: I am on vacation for the next two weeks)

Copy link
Contributor

@dnfield dnfield left a comment

Choose a reason for hiding this comment

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

LGTM if LGT @goderbauer

@goderbauer
Copy link
Member

You can see the data I have in go/flutter-persistent-hash-map-for-inherited-widgets - its a mix of real world data and microbenchmarks.

Thanks, that was a helpful read. I think this looks promising. Let's give it a try and keep an eye on the benchmarks after this is merged.

LGTM after doc and style comments are addressed.

@dnfield
Copy link
Contributor

dnfield commented Jul 19, 2022

@goderbauer I went ahead and addressed your doc/style nits so we can try to get this landed. @mraleph is going to be unavailable again for a week or two and I don't want this to get lost in the mean time.

@mraleph when you get back if there any further concerns we can try to address them in follow up.

@dnfield dnfield added the autosubmit Merge PR when tree becomes green via auto submit App label Jul 19, 2022
@goderbauer
Copy link
Member

Sounds good. Thanks @dnfield.

engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 22, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 22, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 22, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 22, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 22, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Jul 22, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 23, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 23, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 24, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 24, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 25, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 25, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 25, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 25, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 25, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 25, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 25, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 25, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 25, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 25, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 25, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 25, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 26, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 26, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 26, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 26, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 26, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Jul 26, 2022
camsim99 pushed a commit to camsim99/flutter that referenced this pull request Aug 10, 2022
* Use persistent hash map to store _inheritedWidgets

Instead of using a HashMap and copying it down the tree
which leads to quadratic time and space complexity
use a persistent data structure which can amortize
the cost by sharing parts of the structure.

The data shows HAMT based PersistentHashMap to be
5-10x faster for building _inheritedWidgets and
considerably more space effecient (e.g. bringing
amount of memory allocated when constructing
_inheritedWidgets in a tree with 150 InheritedWidget
down to 70Kb from 970Kb).

PersistentHashMap is slower than HashMap for
access: 2-3x in relative terms, but in absolute
terms we are only talking about ~0.2ns slow down
per access and various app benchmarks we run have
have not revealed any significant regressions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autosubmit Merge PR when tree becomes green via auto submit App framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants