From 7b6b0e5dedb53c73fff668ca02909a2f709d4c29 Mon Sep 17 00:00:00 2001 From: Kasper Lund Date: Fri, 13 Dec 2013 14:20:37 +0100 Subject: [PATCH] perf(digest): Use linked list for watchers Performance This makes it a lot faster to add and remove watchers and it doesn't seem to slow down the digest loop. dart2js (new: linked watchers) noop: => 153,147,355,556 ops/sec (0 us) stdev(415.89679) empty scope $digest(): => 567,981 ops/sec (2 us) stdev(0.01002) adding/removing 4000 watchers: => 406 ops/sec (2,461 us) stdev(0) 4000 dummy watchers on scope: => 7,680 ops/sec (130 us) stdev(0.0002) 3000 watchers on scope: => 201 ops/sec (4,965 us) stdev(0.00001) dart2js (old: non-linked watchers) noop: => 153,222,244,444 ops/sec (0 us) stdev(150.92445) empty scope $digest(): => 537,580 ops/sec (2 us) stdev(0.01016) adding/removing 4000 watchers: => 1 ops/sec (1,327,937 us) stdev(0) 4000 dummy watchers on scope: => 4,091 ops/sec (244 us) stdev(0.00015) 3000 watchers on scope: => 177 ops/sec (5,638 us) stdev(0.00001) --- lib/core/scope.dart | 112 +++++++++++++++++++++++++++---------------- perf/_perf.dart | 3 +- perf/scope_perf.dart | 10 ++++ 3 files changed, 84 insertions(+), 41 deletions(-) diff --git a/lib/core/scope.dart b/lib/core/scope.dart index 0d98b3756..827ba851a 100644 --- a/lib/core/scope.dart +++ b/lib/core/scope.dart @@ -45,7 +45,7 @@ class Scope implements Map { final NgZone _zone; final num _ttl; final Map _properties = {}; - final List<_Watch> _watchers = []; + final _WatchList _watchers = new _WatchList(); final Map> _listeners = {}; final bool _isolate; final bool _lazy; @@ -208,11 +208,7 @@ class Scope implements Map { } var watcher = new _Watch(_compileToFn(listener), _initWatchVal, _compileToFn(watchExpression), watchStr); - - // we use unshift since we use a while loop in $digest for speed. - // the while loop reads in reverse order. - _watchers.insert(0, watcher); - + _watchers.addLast(watcher); return () => _watchers.remove(watcher); } @@ -404,12 +400,10 @@ class Scope implements Map { $digest() { var innerAsyncQueue = _innerAsyncQueue; - int length; _Watch lastDirtyWatch = null; _Watch lastLoopLastDirtyWatch; int _ttlLeft = _ttl; List> watchLog = []; - List<_Watch> watchers; _Watch watch; Scope next, current, target = this; @@ -443,30 +437,27 @@ class Scope implements Map { digestLoop: do { // "traverse the scopes" loop scopeCount++; - if ((watchers = current._watchers) != null) { - // process our watches - length = watchers.length; - watcherCount += length; - while (length-- > 0) { - try { - watch = watchers[length]; - if (identical(lastLoopLastDirtyWatch, watch)) { - break digestLoop; - } - var value = watch.get(current); - var last = watch.last; - if (!_identical(value, last)) { - lastDirtyWatch = watch; - lastLoopLastDirtyWatch = null; - watch.last = value; - var fireTimer; - assert((fireTimer = _perf.startTimer('ng.fire', watch.exp)) != false); - watch.fn(value, ((last == _initWatchVal) ? value : last), current); - assert(_perf.stopTimer(fireTimer) != false); - } - } catch (e, s) { - _exceptionHandler(e, s); + // process our watches + _WatchList watchers = current._watchers; + watcherCount += watchers.length; + for (_Watch watch = watchers.head; watch != null; watch = watch.next) { + try { + if (identical(lastLoopLastDirtyWatch, watch)) { + break digestLoop; } + var value = watch.get(current); + var last = watch.last; + if (!_identical(value, last)) { + lastDirtyWatch = watch; + lastLoopLastDirtyWatch = null; + watch.last = value; + var fireTimer; + assert((fireTimer = _perf.startTimer('ng.fire', watch.exp)) != false); + watch.fn(value, ((last == _initWatchVal) ? value : last), current); + assert(_perf.stopTimer(fireTimer) != false); + } + } catch (e, s) { + _exceptionHandler(e, s); } } @@ -708,17 +699,58 @@ class Scope implements Map { } } -var _initWatchVal = new Object(); +class _InitWatchVal { const _InitWatchVal(); } +const _initWatchVal = const _InitWatchVal(); class _Watch { - Function fn; - dynamic last; - Function get; - String exp; - - _Watch(fn, this.last, getFn, this.exp) { - this.fn = relaxFnArgs3(fn); - this.get = relaxFnArgs1(getFn); + final Function fn; + final Function get; + final String exp; + var last; + + _Watch previous; + _Watch next; + + _Watch(fn, this.last, getFn, this.exp) + : this.fn = relaxFnArgs3(fn) + , this.get = relaxFnArgs1(getFn); +} + +class _WatchList { + int length = 0; + _Watch head; + _Watch tail; + + void addLast(_Watch watch) { + assert(watch.previous == null); + assert(watch.next == null); + if (tail == null) { + tail = head = watch; + } else { + watch.previous = tail; + tail.next = watch; + tail = watch; + } + length++; + } + + void remove(_Watch watch) { + if (watch == head) { + _Watch next = watch.next; + if (next == null) tail = null; + else next.previous = null; + head = next; + } else if (watch == tail) { + _Watch previous = watch.previous; + previous.next = null; + tail = previous; + } else { + _Watch next = watch.next; + _Watch previous = watch.previous; + previous.next = next; + next.previous = previous; + } + length--; } } diff --git a/perf/_perf.dart b/perf/_perf.dart index 7b2076b6f..aaa7437f4 100644 --- a/perf/_perf.dart +++ b/perf/_perf.dart @@ -51,7 +51,8 @@ measure(b) { } } stopwatch.stop(); - return new Sample(count, stopwatch.elapsedMicroseconds); + int elapsed = max(1, stopwatch.elapsedMicroseconds); + return new Sample(count, elapsed); } main() {} diff --git a/perf/scope_perf.dart b/perf/scope_perf.dart index c51eda26d..888060d10 100644 --- a/perf/scope_perf.dart +++ b/perf/scope_perf.dart @@ -33,6 +33,16 @@ main() { scope.a = new A(); + List watchFns = new List.generate(4000, (i) => () => i); + time('adding/removing 4000 watchers', () { + List watchers = watchFns.map(scope.$watch).toList(); + watchers.forEach((e) => e()); + }); + + List watchers = watchFns.map(scope.$watch).toList(); + time('4000 dummy watchers on scope', () => scope.$digest()); + watchers.forEach((e) => e()); + for(var i = 0; i < 1000; i++ ) { scope.$watch('a.number', () => null); scope.$watch('a.str', () => null);