Unverified Commit 81300293 authored by Vyacheslav Egorov's avatar Vyacheslav Egorov Committed by GitHub

Use persistent hash map to store _inheritedWidgets (#107068)

* 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.
parent 5b94f254
......@@ -37,6 +37,7 @@ export 'src/foundation/math.dart';
export 'src/foundation/node.dart';
export 'src/foundation/object.dart';
export 'src/foundation/observer_list.dart';
export 'src/foundation/persistent_hash_map.dart';
export 'src/foundation/platform.dart';
export 'src/foundation/print.dart';
export 'src/foundation/serialization.dart';
......
This diff is collapsed.
......@@ -4231,7 +4231,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
return null;
}
Map<Type, InheritedElement>? _inheritedWidgets;
PersistentHashMap<Type, InheritedElement>? _inheritedWidgets;
Set<InheritedElement>? _dependencies;
bool _hadUnsatisfiedDependencies = false;
......@@ -5319,13 +5319,9 @@ class InheritedElement extends ProxyElement {
@override
void _updateInheritance() {
assert(_lifecycleState == _ElementLifecycle.active);
final Map<Type, InheritedElement>? incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null) {
_inheritedWidgets = HashMap<Type, InheritedElement>.of(incomingWidgets);
} else {
_inheritedWidgets = HashMap<Type, InheritedElement>();
}
_inheritedWidgets![widget.runtimeType] = this;
final PersistentHashMap<Type, InheritedElement> incomingWidgets =
_parent?._inheritedWidgets ?? const PersistentHashMap<Type, InheritedElement>.empty();
_inheritedWidgets = incomingWidgets.put(widget.runtimeType, this);
}
@override
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
@immutable
class _MockKey {
const _MockKey({required this.hashCode, required this.payload});
@override
final int hashCode;
final String payload;
@override
bool operator ==(Object other) {
return other is _MockKey && other.payload == payload;
}
}
void main() {
test('PersistentHashMap - Simple Test', () {
final List<PersistentHashMap<String, int>> maps =
<PersistentHashMap<String, int>>[];
maps.add(const PersistentHashMap<String, int>.empty());
for (int i = 0; i < 50; i++) {
maps.add(maps.last.put('key:$i', i));
}
for (int i = 1; i < maps.length; i++) {
final PersistentHashMap<String, int> m = maps[i];
for (int j = 0; j < i; j++) {
expect(m['key:$j'], equals(j));
}
}
});
test('PersistentHashMap - hash collisions', () {
const _MockKey key1 = _MockKey(hashCode: 1, payload: 'key:1');
const _MockKey key2 = _MockKey(hashCode: 0 | (1 << 5), payload: 'key:2');
const _MockKey key3 = _MockKey(hashCode: 1, payload: 'key:3');
const _MockKey key4 = _MockKey(hashCode: 1 | (1 << 5), payload: 'key:4');
final PersistentHashMap<_MockKey, String> map =
const PersistentHashMap<_MockKey, String>.empty()
.put(key1, 'a')
.put(key2, 'b')
.put(key3, 'c');
expect(map[key1], equals('a'));
expect(map[key2], equals('b'));
expect(map[key3], equals('c'));
final PersistentHashMap<_MockKey, String> map2 = map.put(key4, 'd');
expect(map2[key4], equals('d'));
final PersistentHashMap<_MockKey, String> map3 = map2
.put(key1, 'updated(a)')
.put(key2, 'updated(b)')
.put(key3, 'updated(c)')
.put(key4, 'updated(d)');
expect(map3[key1], equals('updated(a)'));
expect(map3[key2], equals('updated(b)'));
expect(map3[key3], equals('updated(c)'));
expect(map3[key4], equals('updated(d)'));
});
test('PersistentHashMap - inflation of nodes', () {
final List<PersistentHashMap<_MockKey, int>> maps =
<PersistentHashMap<_MockKey, int>>[];
maps.add(const PersistentHashMap<_MockKey, int>.empty());
for (int i = 0; i < 32 * 32; i++) {
maps.add(maps.last.put(_MockKey(hashCode: i, payload: '$i'), i));
}
for (int i = 1; i < maps.length; i++) {
final PersistentHashMap<_MockKey, int> m = maps[i];
for (int j = 0; j < i; j++) {
expect(m[_MockKey(hashCode: j, payload: '$j')], equals(j));
}
}
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment