diff --git a/benchmark/pubspec.lock b/benchmark/pubspec.lock
index cdf0619ed..d29fe4deb 100644
--- a/benchmark/pubspec.lock
+++ b/benchmark/pubspec.lock
@@ -70,7 +70,7 @@ packages:
route_hierarchical:
description: route_hierarchical
source: hosted
- version: "0.4.20"
+ version: "0.4.21"
source_maps:
description: source_maps
source: hosted
diff --git a/benchmark/pubspec.yaml b/benchmark/pubspec.yaml
index ca22114fd..141c3f449 100644
--- a/benchmark/pubspec.yaml
+++ b/benchmark/pubspec.yaml
@@ -1,4 +1,4 @@
-name: angular-perf
+name: angularperf
version: 0.10.0
description: Angular benchmarks
dependencies:
@@ -8,3 +8,8 @@ dev_dependencies:
benchmark_harness: '>=1.0.0'
unittest: '>=0.10.1 <0.12.0'
mock: '>=0.10.0 <0.12.0'
+transformers:
+- angular
+- $dart2js:
+ minify: false
+ checked: false
diff --git a/benchmark/web/bp.js b/benchmark/web/bp.js
new file mode 100644
index 000000000..c81c20366
--- /dev/null
+++ b/benchmark/web/bp.js
@@ -0,0 +1,136 @@
+window.benchmarkSteps = [];
+
+window.addEventListener('DOMContentLoaded', function() {
+ var container = document.querySelector('#benchmarkContainer');
+
+ // Add links to everything
+ var linkDiv = document.createElement('div');
+ linkDiv.style['margin-bottom'] = "1.5em";
+ var linkHtml = [
+ '',
+ 'Benchmark Versions: '
+ ].join('\n');
+
+ [
+ // Add new benchmark suites here
+ ['tree.html', 'TreeComponent']
+ ].forEach((function (link) {
+ linkHtml += [
+ '',
+ link[1],
+ ''
+ ].join('');
+ }));
+
+ linkDiv.innerHTML = linkHtml;
+ container.appendChild(linkDiv);
+
+
+ // Benchmark runner
+ var btn = document.createElement('button');
+ btn.innerText = "Loop";
+ var running = false;
+ btn.addEventListener('click', loopBenchmark);
+
+ container.appendChild(btn);
+
+ function loopBenchmark() {
+ if (running) {
+ btn.innerText = "Loop";
+ running = false;
+ } else {
+ window.requestAnimationFrame(function() {
+ btn.innerText = "Pause";
+ running = true;
+ var loopB = function() {
+ if (running) {
+ window.requestAnimationFrame(function() {
+ if (running) runBenchmarkSteps(loopB);
+ });
+ }
+ };
+ loopB();
+ });
+ }
+ }
+
+
+ var onceBtn = document.createElement('button');
+ onceBtn.innerText = "Once";
+ onceBtn.addEventListener('click', function() {
+ window.requestAnimationFrame(function() {
+ onceBtn.innerText = "...";
+ window.requestAnimationFrame(function() {
+ runBenchmarkSteps(function() {
+ onceBtn.innerText = "Once";
+ });
+ });
+ });
+ });
+ container.appendChild(onceBtn);
+
+ var infoDiv = document.createElement('div');
+ infoDiv.style['font-family'] = 'monospace';
+ container.appendChild(infoDiv);
+
+
+ var numMilliseconds;
+ var performance = window.performance;
+ if (performance != null && typeof performance.now == "function") {
+ numMilliseconds = function numMillisecondsWPN() {
+ return performance.now();
+ }
+ } else if (performance != null && typeof performance.webkitNow == "function") {
+ numMilliseconds = function numMillisecondsWebkit() {
+ return performance.webkitNow();
+ }
+ } else {
+ console.log('using Date.now');
+ numMilliseconds = function numMillisecondsDateNow() {
+ return Date.now();
+ };
+ }
+
+ function runBenchmarkSteps(done) {
+ // Run all the steps;
+ var times = {};
+ window.benchmarkSteps.forEach(function(bs) {
+ var startTime = numMilliseconds();
+ bs.fn();
+ times[bs.name] = numMilliseconds() - startTime;
+ });
+ calcStats(times);
+
+ done();
+ }
+
+ var timesPerAction = {};
+
+ var NUM_SAMPLES = 10;
+ function calcStats(times) {
+ var iH = '';
+ window.benchmarkSteps.forEach(function(bs) {
+ var tpa = timesPerAction[bs.name];
+ if (!tpa) {
+ tpa = timesPerAction[bs.name] = {
+ times: [], // circular buffer
+ fmtTimes: [],
+ nextEntry: 0
+ }
+ }
+ tpa.fmtTimes[tpa.nextEntry] = ('' + times[bs.name]).substr(0,6);
+ tpa.times[tpa.nextEntry++] = times[bs.name];
+ tpa.nextEntry %= NUM_SAMPLES;
+ var avg = 0;
+ tpa.times.forEach(function(x) { avg += x; });
+ avg /= Math.min(NUM_SAMPLES, tpa.times.length);
+ avg = ('' + avg).substr(0,6);
+ iH += '
' + (' ' + bs.name).slice(-10).replace(/ /g, ' ') + ': avg-' + NUM_SAMPLES + ':' + avg + 'ms [' + tpa.fmtTimes.join(', ') + ']ms
';
+ });
+ infoDiv.innerHTML = iH;
+ }
+});
diff --git a/benchmark/web/tree.dart b/benchmark/web/tree.dart
new file mode 100644
index 000000000..cc3975311
--- /dev/null
+++ b/benchmark/web/tree.dart
@@ -0,0 +1,290 @@
+import 'package:di/di.dart';
+import 'package:angular/angular.dart';
+import 'package:angular/core_dom/module_internal.dart';
+import 'package:angular/application_factory.dart';
+
+import 'dart:html';
+import 'dart:math';
+import 'dart:js' as js;
+
+@Component(
+ selector: 'tree',
+ template: ' {{ctrl.data.value}}'
+ ''
+ ''
+ '',
+ publishAs: 'ctrl')
+class TreeComponent {
+ @NgOneWay('data')
+ var data;
+}
+
+
+// This is a baseline implementation of TreeComponent.
+// It assumes the data never changes and simply throws elements on the DOM
+@Component(
+ selector: 'ng-free-tree',
+ template: ''
+ )
+class NgFreeTree implements ShadowRootAware {
+ var _data;
+
+ @NgOneWay('data')
+ set data(v) {
+ _data = v;
+ if (sroot != null)
+ updateElement(sroot, _data);
+ }
+
+ ShadowRoot sroot;
+
+ void onShadowRoot(root) {
+ sroot = root;
+ if (_data != null) updateElement(sroot, _data);
+ }
+
+ Element newFreeTree(tree) {
+ var elt = new Element.tag('ng-fre-tree');
+ var root = elt.createShadowRoot();
+
+ var s = new SpanElement();
+ root.append(s);
+ var value = tree['value'];
+ if (value != null) {
+ s.text = " $value";
+ }
+ if (tree.containsKey('right')) {
+ s.append(new SpanElement()
+ ..append(newFreeTree(tree['right'])));
+ }
+ if (tree.containsKey('left')) {
+ s.append(new SpanElement()
+ ..append(newFreeTree(tree['left'])));
+ }
+ return elt;
+ }
+
+ updateElement(root, tree) {
+ // Not quite acurate
+ root.innerHtml = '';
+ root.append(newFreeTree(tree));
+ }
+}
+
+/**
+ * A baseline version of TreeComponent which uses Angular's Scope to
+ * manage data. This version is setting up data binding so arbitrary
+ * elements in the tree can change.
+ *
+ * Note that removing subtrees is not implemented as that feature
+ * is never exercised in the benchmark.
+ */
+@Component(
+ selector: 'ng-free-tree-scoped',
+ template: ''
+ )
+class NgFreeTreeScoped implements ShadowRootAware {
+ var _data;
+
+ @NgOneWay('data')
+ set data(v) {
+ _data = v;
+ if (sroot != null)
+ updateElement(sroot, _data);
+ }
+
+ ShadowRoot sroot;
+ Scope scope;
+ NgFreeTreeScoped(Scope this.scope);
+
+ void onShadowRoot(root) {
+ sroot = root;
+ if (_data != null) updateElement(sroot, _data);
+ }
+
+ Element newFreeTree(parentScope, treeExpr) {
+ var elt = new Element.tag('ng-fre-tree');
+ var root = elt.createShadowRoot();
+ var scope = parentScope.createChild({});
+
+ parentScope.watch(treeExpr, (v, _) {
+ scope.context['tree'] = v;
+ });
+
+ var s = new SpanElement();
+ root.append(s);
+ scope.watch('tree.value', (v, _) {
+ if (v != null) {
+ s.text = " $v";
+ }
+ });
+
+ scope.watch('tree.right != null', (v, _) {
+ if (v != true) return;
+ s.append(new SpanElement()
+ ..append(newFreeTree(scope, 'tree.right')));
+ });
+
+ scope.watch('tree.left != null', (v, _) {
+ if (v != true) return;
+ s.append(new SpanElement()
+ ..append(newFreeTree(scope, 'tree.left')));
+ });
+
+ return elt;
+ }
+
+ Scope treeScope;
+ updateElement(root, tree) {
+ // Not quite acurate
+ if (treeScope != null) {
+ treeScope.destroy();
+ }
+ treeScope = scope.createChild({});
+ treeScope.context['tree'] = tree;
+ root.innerHtml = '';
+ root.append(newFreeTree(treeScope, 'tree'));
+ }
+}
+
+
+/**
+ * A scope-backed baseline that data-binds through a Dart object.
+ * This is the pattern that we are using in Components.
+ *
+ * The benchmark does not show this approach as any slower than
+ * binding to the model directly.
+ */
+class FreeTreeClass {
+ // One-way bound
+ var tree;
+ Scope parentScope;
+
+ FreeTreeClass(this.parentScope, treeExpr) {
+ parentScope.watch(treeExpr, (v, _) {
+ tree = v;
+ });
+ }
+
+ Element element() {
+ var elt = new Element.tag('ng-fre-tree');
+ var root = elt.createShadowRoot();
+ var scope = parentScope.createChild(this);
+
+ var s = new SpanElement();
+ root.append(s);
+ scope.watch('tree.value', (v, _) {
+ if (v != null) {
+ s.text = " $v";
+ }
+ });
+
+ scope.watch('tree.right != null', (v, _) {
+ if (v != true) return;
+ s.append(new SpanElement()
+ ..append(new FreeTreeClass(scope, 'tree.right').element()));
+ });
+
+ scope.watch('tree.left != null', (v, _) {
+ if (v != true) return;
+ s.append(new SpanElement()
+ ..append(new FreeTreeClass(scope, 'tree.left').element()));
+ });
+
+ return elt;
+ }
+}
+
+@Component(
+ selector: 'ng-free-tree-class',
+ template: ''
+ )
+class NgFreeTreeClass implements ShadowRootAware {
+ var _data;
+
+ @NgOneWay('data')
+ set data(v) {
+ _data = v;
+ if (sroot != null)
+ updateElement(sroot, _data);
+ }
+
+ ShadowRoot sroot;
+ Scope scope;
+ NgFreeTreeClass(Scope this.scope);
+
+ void onShadowRoot(root) {
+ sroot = root;
+ if (_data != null) updateElement(sroot, _data);
+ }
+
+
+ var treeScope;
+ updateElement(root, tree) {
+ // Not quite acurate
+ if (treeScope != null) {
+ treeScope.destroy();
+ }
+ treeScope = scope.createChild({});
+ treeScope.context['tree'] = tree;
+ root.innerHtml = '';
+ root.append(new FreeTreeClass(treeScope, 'tree').element());
+ }
+}
+
+
+// Main function runs the benchmark.
+main() {
+ var cleanup, createDom;
+
+ var module = new Module()
+ ..type(TreeComponent)
+ ..type(NgFreeTree)
+ ..type(NgFreeTreeScoped)
+ ..type(NgFreeTreeClass)
+ ..factory(ScopeDigestTTL, (i) => new ScopeDigestTTL.value(15));
+
+ var injector = applicationFactory().addModule(module).run();
+ assert(injector != null);
+ VmTurnZone zone = injector.get(VmTurnZone);
+ Scope scope = injector.get(Scope);
+
+ scope.context['initData'] = {
+ "value": "top",
+ "right": {
+ "value": "right"
+ },
+ "left": {
+ "value": "left"
+ }
+ };
+
+ buildTree(maxDepth, values, curDepth) {
+ if (maxDepth == curDepth) return {};
+ return {
+ "value": values[curDepth],
+ "right": buildTree(maxDepth, values, curDepth+1),
+ "left": buildTree(maxDepth, values, curDepth+1)
+
+ };
+ }
+ cleanup = (_) => zone.run(() {
+ scope.context['initData'] = {};
+ });
+
+ var count = 0;
+ createDom = (_) => zone.run(() {
+ var maxDepth = 9;
+ var values = count++ % 2 == 0 ?
+ ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*'] :
+ ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', '-'];
+ scope.context['initData'] = buildTree(maxDepth, values, 0);
+ });
+
+ js.context['benchmarkSteps'].add(new js.JsObject.jsify({
+ "name": "cleanup", "fn": new js.JsFunction.withThis(cleanup)
+ }));
+ js.context['benchmarkSteps'].add(new js.JsObject.jsify({
+ "name": "createDom", "fn": new js.JsFunction.withThis(createDom)
+ }));
+}
diff --git a/benchmark/web/tree.html b/benchmark/web/tree.html
new file mode 100644
index 000000000..f8839c187
--- /dev/null
+++ b/benchmark/web/tree.html
@@ -0,0 +1,32 @@
+
+
+ TreeComponent Benchmark
+
+
+
+
+
+
+
+
+
+ Render a 2^9 prefix tree with AngularDart
+
+
+ Default:
+ Baseline:
+ Baseline + scope:
+ Baseline + class:
+
+
+
+
+
+
+
+
+
+ {{tree}}
+
+
+