Skip to content
This repository has been archived by the owner on Feb 22, 2018. It is now read-only.

Commit

Permalink
perf(digest): Use linked list for watchers
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
Kasper Lund authored and mhevery committed Dec 14, 2013
1 parent 525eead commit 7b6b0e5
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 41 deletions.
112 changes: 72 additions & 40 deletions lib/core/scope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class Scope implements Map {
final NgZone _zone;
final num _ttl;
final Map<String, Object> _properties = {};
final List<_Watch> _watchers = [];
final _WatchList _watchers = new _WatchList();
final Map<String, List<Function>> _listeners = {};
final bool _isolate;
final bool _lazy;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -404,12 +400,10 @@ class Scope implements Map {

$digest() {
var innerAsyncQueue = _innerAsyncQueue;
int length;
_Watch lastDirtyWatch = null;
_Watch lastLoopLastDirtyWatch;
int _ttlLeft = _ttl;
List<List<String>> watchLog = [];
List<_Watch> watchers;
_Watch watch;
Scope next, current, target = this;

Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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--;
}
}

Expand Down
3 changes: 2 additions & 1 deletion perf/_perf.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
Expand Down
10 changes: 10 additions & 0 deletions perf/scope_perf.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 7b6b0e5

Please sign in to comment.