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

feat(WatchGroup): Allow measuring cost of running reaction functions and #1148

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions benchmark/watch_group_perf.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class _CollectionCheck extends BenchmarkBase {

_fieldRead() {
var watchGrp = new RootWatchGroup(_dynamicFieldGetterFactory,
new DirtyCheckingChangeDetector(_dynamicFieldGetterFactory), new _Obj())
new DirtyCheckingChangeDetector(_dynamicFieldGetterFactory), new _Obj(), null)
..watch(_parse('a'), _reactionFn)
..watch(_parse('b'), _reactionFn)
..watch(_parse('c'), _reactionFn)
Expand Down Expand Up @@ -80,7 +80,7 @@ _fieldRead() {

_fieldReadGetter() {
var watchGrp= new RootWatchGroup(_staticFieldGetterFactory,
new DirtyCheckingChangeDetector(_staticFieldGetterFactory), new _Obj())
new DirtyCheckingChangeDetector(_staticFieldGetterFactory), new _Obj(), null)
..watch(_parse('a'), _reactionFn)
..watch(_parse('b'), _reactionFn)
..watch(_parse('c'), _reactionFn)
Expand Down Expand Up @@ -114,7 +114,7 @@ _mapRead() {
'k': 0, 'l': 1, 'm': 2, 'n': 3, 'o': 4,
'p': 0, 'q': 1, 'r': 2, 's': 3, 't': 4};
var watchGrp = new RootWatchGroup(_dynamicFieldGetterFactory,
new DirtyCheckingChangeDetector(_dynamicFieldGetterFactory), map)
new DirtyCheckingChangeDetector(_dynamicFieldGetterFactory), map, null)
..watch(_parse('a'), _reactionFn)
..watch(_parse('b'), _reactionFn)
..watch(_parse('c'), _reactionFn)
Expand Down Expand Up @@ -144,7 +144,7 @@ _methodInvoke0() {
var context = new _Obj();
context.a = new _Obj();
var watchGrp = new RootWatchGroup(_dynamicFieldGetterFactory,
new DirtyCheckingChangeDetector(_dynamicFieldGetterFactory), context)
new DirtyCheckingChangeDetector(_dynamicFieldGetterFactory), context, null)
..watch(_method('a', 'methodA'), _reactionFn)
..watch(_method('a', 'methodB'), _reactionFn)
..watch(_method('a', 'methodC'), _reactionFn)
Expand Down Expand Up @@ -174,7 +174,7 @@ _methodInvoke1() {
var context = new _Obj();
context.a = new _Obj();
var watchGrp = new RootWatchGroup(_dynamicFieldGetterFactory,
new DirtyCheckingChangeDetector(_dynamicFieldGetterFactory), context)
new DirtyCheckingChangeDetector(_dynamicFieldGetterFactory), context, null)
..watch(_method('a', 'methodA1', [_parse('a')]), _reactionFn)
..watch(_method('a', 'methodB1', [_parse('a')]), _reactionFn)
..watch(_method('a', 'methodC1', [_parse('a')]), _reactionFn)
Expand Down Expand Up @@ -203,7 +203,7 @@ _methodInvoke1() {
_function2() {
var context = new _Obj();
var watchGrp = new RootWatchGroup(_dynamicFieldGetterFactory,
new DirtyCheckingChangeDetector(_dynamicFieldGetterFactory), context)
new DirtyCheckingChangeDetector(_dynamicFieldGetterFactory), context, null)
..watch(_add(0, _parse('a'), _parse('a')), _reactionFn)
..watch(_add(1, _parse('a'), _parse('a')), _reactionFn)
..watch(_add(2, _parse('a'), _parse('a')), _reactionFn)
Expand Down
4 changes: 3 additions & 1 deletion example/web/todo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ main() {
print(window.location.search);
var module = new Module()
..bind(Todo)
..bind(PlaybackHttpBackendConfig);
..bind(PlaybackHttpBackendConfig)
..bind(ExecutionStats, toFactory: (i) => new ExecutionStats(15, i.get(ExecutionStatsEmitter)))
..bind(ExecutionStatsEmitter);

// If these is a query in the URL, use the server-backed
// TodoController. Otherwise, use the stored-data controller.
Expand Down
5 changes: 3 additions & 2 deletions lib/application.dart
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,7 @@ abstract class Application {
}

Injector run() {
publishToJavaScript();
return zone.run(() {
var injector = zone.run(() {
var rootElements = [element];
Injector injector = createInjector();
ExceptionHandler exceptionHandler = injector.getByKey(EXCEPTION_HANDLER_KEY);
Expand All @@ -178,6 +177,8 @@ abstract class Application {
});
return injector;
});
publishToJavaScript(injector);
return injector;
}

/**
Expand Down
3 changes: 3 additions & 0 deletions lib/change_detection/change_detection.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
library change_detection;

import 'package:angular/change_detection/execution_stats.dart';

typedef void EvalExceptionHandler(error, stack);

/**
Expand Down Expand Up @@ -54,6 +56,7 @@ abstract class ChangeDetector<H> extends ChangeDetectorGroup<H> {
* same order as they were registered.
*/
Iterator<Record<H>> collectChanges({EvalExceptionHandler exceptionHandler,
ExecutionStats executionStats, Stopwatch executionStopwatch,
AvgStopwatch stopwatch });
}

Expand Down
17 changes: 16 additions & 1 deletion lib/change_detection/dirty_checking_change_detector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ library dirty_checking_change_detector;

import 'dart:collection';
import 'package:angular/change_detection/change_detection.dart';
import 'package:angular/change_detection/execution_stats.dart';

/**
* [DirtyCheckingChangeDetector] determines which object properties have changed
Expand Down Expand Up @@ -304,6 +305,8 @@ class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H>
}

Iterator<Record<H>> collectChanges({EvalExceptionHandler exceptionHandler,
ExecutionStats executionStats,
Stopwatch executionStopwatch,
AvgStopwatch stopwatch}) {
if (stopwatch != null) stopwatch.start();
DirtyCheckingRecord changeTail = _fakeHead;
Expand All @@ -312,7 +315,19 @@ class DirtyCheckingChangeDetector<H> extends DirtyCheckingChangeDetectorGroup<H>
int count = 0;
while (current != null) {
try {
if (current.check()) changeTail = changeTail._nextChange = current;
if (executionStats != null && executionStats.config.enabled) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think a better way to do this is to duplicate the whole while loop. One while loop is instrumented and the other is not.

The stats objects should be injected in the constructor and should have on off switch which would then control which while loop to chose. That way one could turn on/off the stats during the lifetime.

executionStopwatch.reset();
executionStopwatch.start();
}
var check = current.check();
if (executionStats != null && executionStats.config.enabled) {
executionStopwatch.stop();
if (executionStopwatch.elapsedMicroseconds >= executionStats.config.threshold) {
executionStats.addDirtyCheckEntry(new ExecutionEntry(
executionStopwatch.elapsedMicroseconds, current));
}
}
if (check) changeTail = changeTail._nextChange = current;
count++;
} catch (e, s) {
if (exceptionHandler == null) {
Expand Down
143 changes: 143 additions & 0 deletions lib/change_detection/execution_stats.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
library angular.change_detection.execution_stats;

import 'package:angular/core/annotation_src.dart';

@Injectable()
class ExecutionStats {
final ExecutionStatsConfig config;
final ExecutionStatsEmitter emitter;
List<ExecutionEntry> _dirtyCheckStats;
List<ExecutionEntry> _dirtyWatchStats;
List<ExecutionEntry> _evalStats;
int _evalsCount = 0;
int _dirtyWatchCount = 0;
int _dirtyCheckCount = 0;

int get _capacity => config.maxEntries;

ExecutionStats(this.emitter, this.config) {
reset();
}

void addDirtyCheckEntry(ExecutionEntry entry) {
if( ++_dirtyCheckCount >= _capacity) _shrinkDirtyCheck();
_dirtyCheckStats[_dirtyCheckCount] = entry;
}

void addDirtyWatchEntry(ExecutionEntry entry) {
if( ++_dirtyWatchCount >= _capacity) _shrinkDirtyWatch();
_dirtyWatchStats[_dirtyWatchCount] = entry;
}

void addEvalEntry(ExecutionEntry entry) {
if( ++_evalsCount >= _capacity) _shrinkEval();
_evalStats[_evalsCount] = entry;
}

void showEvalStats() {
emitter.showEvalStats(this);
}

void showReactionFnStats() {
emitter.showReactionFnStats(this);
}

void showDirtyCheckStats() {
emitter.showDirtyCheckStats(this);
}

Iterable<ExecutionEntry> get dirtyCheckStats {
_shrinkDirtyWatch();
return _dirtyCheckStats.getRange(0, _capacity).where((e) => e.time > 0);
}

Iterable<ExecutionEntry> get evalStats {
_shrinkDirtyWatch();
return _evalStats.getRange(0, _capacity).where((e) => e.time > 0);
}

Iterable<ExecutionEntry> get reactionFnStats {
_shrinkDirtyWatch();
return _dirtyWatchStats.getRange(0, _capacity).where((e) => e.time > 0);
}

void enable() {
config.enabled = true;
}

void disable() {
config.enabled = false;
}

void reset() {
_dirtyCheckStats = new List.filled(3 * _capacity, new ExecutionEntry(0, null));
_dirtyWatchStats = new List.filled(3 * _capacity, new ExecutionEntry(0, null));
_evalStats = new List.filled(3 * _capacity, new ExecutionEntry(0, null));
_evalsCount = 0;
_dirtyWatchCount = 0;
_dirtyCheckCount = 0;
}

void _shrinkDirtyCheck() {
_dirtyCheckStats.sort((ExecutionEntry x, ExecutionEntry y) => y.time.compareTo(x.time));
for(int i = _capacity; i < 3 * _capacity; i++) _dirtyCheckStats[i] = new ExecutionEntry(0, null);
_dirtyCheckCount = _capacity;
}

void _shrinkDirtyWatch() {
_dirtyWatchStats.sort((ExecutionEntry x, ExecutionEntry y) => x.time.compareTo(y.time) * -1);
for(int i = _capacity; i < 3 * _capacity; i++) _dirtyWatchStats[i] = new ExecutionEntry(0, null);
_dirtyWatchCount = _capacity;
}

void _shrinkEval() {
_evalStats.sort((ExecutionEntry x, ExecutionEntry y) => x.time.compareTo(y.time) * -1);
for(int i = _capacity; i < 3 * _capacity; i++) _evalStats[i] = new ExecutionEntry(0, null);
_evalsCount = _capacity;
}
}

@Injectable()
class ExecutionStatsEmitter {
void showDirtyCheckStats(ExecutionStats fnStats) {
_printLine('Time (us)', 'Field');
fnStats.dirtyCheckStats.forEach((ExecutionEntry entry) =>
_printLine('${entry.time}', '${entry.value}'));
Copy link
Contributor

Choose a reason for hiding this comment

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

missing 4ws

}

void showEvalStats(ExecutionStats fnStats) {
_printLine('Time (us)', 'Name');
fnStats.evalStats.forEach((ExecutionEntry entry) =>
_printLine('${entry.time}', '${entry.value}'));
}

void showReactionFnStats(ExecutionStats fnStats) {
_printLine('Time (us)', 'Expression');
fnStats.reactionFnStats.forEach((ExecutionEntry entry) =>
_printLine('${entry.time}', '${entry.value}'));
}

_printLine(String first, String second) {
var timesColLength = 10;
var expressionsColPrefix = 5;
var timesCol = ' ' * (timesColLength - first.length);
var expressionsCol = ' ' * expressionsColPrefix;
print('${timesCol + first}${expressionsCol + second}');
}

}

class ExecutionEntry {
final num time;
final dynamic value; //Record or Watch

ExecutionEntry(this.time, this.value);
}

class ExecutionStatsConfig {
bool enabled;
int threshold;
int maxEntries;

ExecutionStatsConfig({this.enabled: false, this.threshold, this.maxEntries: 15});
}
44 changes: 38 additions & 6 deletions lib/change_detection/watch_group.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
library angular.watch_group;

import 'package:angular/change_detection/change_detection.dart';
import 'dart:collection';
import 'package:angular/change_detection/change_detection.dart';
import 'package:angular/change_detection/execution_stats.dart';

part 'linked_list.dart';
part 'ast.dart';
Expand Down Expand Up @@ -367,6 +368,8 @@ class WatchGroup implements _EvalWatchList, _WatchGroupList {
class RootWatchGroup extends WatchGroup {
final FieldGetterFactory _fieldGetterFactory;
Watch _dirtyWatchHead, _dirtyWatchTail;
Stopwatch executionStopwatch;
final ExecutionStats executionStats;

/**
* Every time a [WatchGroup] is destroyed we increment the counter. During
Expand All @@ -378,10 +381,10 @@ class RootWatchGroup extends WatchGroup {
int _removeCount = 0;


RootWatchGroup(this._fieldGetterFactory,
ChangeDetector changeDetector,
Object context)
: super._root(changeDetector, context);
RootWatchGroup(this._fieldGetterFactory, ChangeDetector changeDetector, Object context,
this.executionStats) : super._root(changeDetector, context) {
if (executionStats != null) executionStopwatch = new Stopwatch();
}

RootWatchGroup get _rootGroup => this;

Expand All @@ -405,6 +408,8 @@ class RootWatchGroup extends WatchGroup {
Iterator<Record<_Handler>> changedRecordIterator =
(_changeDetector as ChangeDetector<_Handler>).collectChanges(
exceptionHandler:exceptionHandler,
executionStats: executionStats,
executionStopwatch: executionStopwatch,
stopwatch: fieldStopwatch);
if (processStopwatch != null) processStopwatch.start();
while (changedRecordIterator.moveNext()) {
Expand All @@ -423,7 +428,19 @@ class RootWatchGroup extends WatchGroup {
while (evalRecord != null) {
try {
if (evalStopwatch != null) evalCount++;
if (evalRecord.check() && changeLog != null) {
if (executionStats != null && executionStats.config.enabled) {
executionStopwatch.reset();
executionStopwatch.start();
}
var evalCheck = evalRecord.check();
if (executionStats != null && executionStats.config.enabled) {
executionStopwatch.stop();
if (executionStopwatch.elapsedMicroseconds >= executionStats.config.threshold) {
executionStats.addEvalEntry(new ExecutionEntry(
executionStopwatch.elapsedMicroseconds, evalRecord));
}
}
if (evalCheck && changeLog != null) {
changeLog(evalRecord.handler.expression,
evalRecord.currentValue,
evalRecord.previousValue);
Expand All @@ -448,7 +465,18 @@ class RootWatchGroup extends WatchGroup {
count++;
try {
if (root._removeCount == 0 || dirtyWatch._watchGroup.isAttached) {
if (executionStats != null && executionStats.config.enabled) {
executionStopwatch.reset();
executionStopwatch.start();
}
dirtyWatch.invoke();
if (executionStats != null && executionStats.config.enabled) {
executionStopwatch.stop();
if (executionStopwatch.elapsedMicroseconds >= executionStats.config.threshold) {
executionStats.addDirtyWatchEntry(new ExecutionEntry(
executionStopwatch.elapsedMicroseconds, dirtyWatch));
}
}
}
} catch (e, s) {
if (exceptionHandler == null) rethrow; else exceptionHandler(e, s);
Expand Down Expand Up @@ -516,6 +544,10 @@ class Watch {
_WatchList._remove(handler, this);
handler.release();
}

String toString() {
return '${_watchGroup.id}:${expression}';
}
}

/**
Expand Down
3 changes: 3 additions & 0 deletions lib/core/module.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ library angular.core;
export "package:angular/change_detection/watch_group.dart" show
ReactionFn;

export 'package:angular/change_detection/execution_stats.dart' show
ExecutionStats, ExecutionStatsEmitter, ExecutionStatsConfig;

export "package:angular/core/parser/parser.dart" show
Parser;

Expand Down
Loading