diff --git a/modules/benchmarks/e2e_test/static_tree.dart b/modules/benchmarks/e2e_test/static_tree.dart
new file mode 100644
index 0000000000..03b9f81738
--- /dev/null
+++ b/modules/benchmarks/e2e_test/static_tree.dart
@@ -0,0 +1,5 @@
+library benchmarks.e2e_test.static_tree_perf;
+
+main() {
+
+}
diff --git a/modules/benchmarks/e2e_test/static_tree.ts b/modules/benchmarks/e2e_test/static_tree.ts
new file mode 100644
index 0000000000..20adaa2208
--- /dev/null
+++ b/modules/benchmarks/e2e_test/static_tree.ts
@@ -0,0 +1,54 @@
+import {runClickBenchmark, verifyNoBrowserErrors} from 'angular2/src/test_lib/perf_util';
+
+describe('ng2 static tree benchmark', function() {
+
+ var URL = 'benchmarks/src/static_tree/tree_benchmark.html';
+
+ afterEach(verifyNoBrowserErrors);
+
+ it('should log the ng stats with viewcache', function(done) {
+ runClickBenchmark({
+ url: URL,
+ buttons: ['#ng2DestroyDom', '#ng2CreateDom'],
+ id: 'ng2.static.tree.create.viewcache',
+ params: [{name: 'viewcache', value: 'true'}]
+ }).then(done, done.fail);
+ });
+
+ it('should log the ng stats without viewcache', function(done) {
+ runClickBenchmark({
+ url: URL,
+ buttons: ['#ng2DestroyDom', '#ng2CreateDom'],
+ id: 'ng2.static.tree.create.plain',
+ params: [{name: 'viewcache', value: 'false'}]
+ }).then(done, done.fail);
+ });
+
+ it('should log the ng stats (update)', function(done) {
+ runClickBenchmark({
+ url: URL,
+ buttons: ['#ng2CreateDom'],
+ id: 'ng2.static.tree.update',
+ params: [{name: 'viewcache', value: 'true'}]
+ }).then(done, done.fail);
+ });
+
+ it('should log the baseline stats', function(done) {
+ runClickBenchmark({
+ url: URL,
+ buttons: ['#baselineDestroyDom', '#baselineCreateDom'],
+ id: 'baseline.tree.create',
+ params: [{name: 'depth', value: 9, scale: 'log2'}]
+ }).then(done, done.fail);
+ });
+
+ it('should log the baseline stats (update)', function(done) {
+ runClickBenchmark({
+ url: URL,
+ buttons: ['#baselineCreateDom'],
+ id: 'baseline.tree.update',
+ params: [{name: 'depth', value: 9, scale: 'log2'}]
+ }).then(done, done.fail);
+ });
+
+});
diff --git a/modules/benchmarks/src/index.html b/modules/benchmarks/src/index.html
index 74df5f249b..d0fe3d1877 100644
--- a/modules/benchmarks/src/index.html
+++ b/modules/benchmarks/src/index.html
@@ -20,6 +20,9 @@
Tree benchmark
+
+ Static tree benchmark
+
Naive infinite scroll benchmark
diff --git a/modules/benchmarks/src/static_tree/tree_benchmark.html b/modules/benchmarks/src/static_tree/tree_benchmark.html
new file mode 100644
index 0000000000..2c8e2cbaf8
--- /dev/null
+++ b/modules/benchmarks/src/static_tree/tree_benchmark.html
@@ -0,0 +1,45 @@
+
+
+
+
+Params
+
+
+Angular2 static tree benchmark (depth 10)
+
+
+
+
+
+
+
+Baseline static tree benchmark (depth 10)
+
+
+
+
+
+
+
+
+
+
+
+
+
+$SCRIPTS$
+
+
diff --git a/modules/benchmarks/src/static_tree/tree_benchmark.ts b/modules/benchmarks/src/static_tree/tree_benchmark.ts
new file mode 100644
index 0000000000..5584453222
--- /dev/null
+++ b/modules/benchmarks/src/static_tree/tree_benchmark.ts
@@ -0,0 +1,316 @@
+import {bootstrap, Compiler, Component, Directive, View, ViewContainerRef} from 'angular2/angular2';
+
+import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle';
+import {reflector} from 'angular2/src/reflection/reflection';
+import {ReflectionCapabilities} from 'angular2/src/reflection/reflection_capabilities';
+import {DOM} from 'angular2/src/dom/dom_adapter';
+import {List} from 'angular2/src/facade/collection';
+import {window, document, gc} from 'angular2/src/facade/browser';
+import {
+ getIntParameter,
+ getStringParameter,
+ bindAction,
+ windowProfile,
+ windowProfileEnd
+} from 'angular2/src/test_lib/benchmark_util';
+import {NgIf} from 'angular2/directives';
+import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
+import {APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool';
+import {bind, Binding} from 'angular2/di';
+
+function createBindings(): List {
+ var viewCacheCapacity = getStringParameter('viewcache') == 'true' ? 10000 : 0;
+ return [bind(APP_VIEW_POOL_CAPACITY).toValue(viewCacheCapacity)];
+}
+
+function setupReflector() {
+ reflector.reflectionCapabilities = new ReflectionCapabilities();
+}
+
+const MAX_DEPTH = 10;
+
+export function main() {
+ BrowserDomAdapter.makeCurrent();
+
+ setupReflector();
+
+ var app;
+ var lifeCycle;
+ var baselineRootTreeComponent;
+ var count = 0;
+
+ function profile(create, destroy, name) {
+ return function() {
+ windowProfile(name + ' w GC');
+ var duration = 0;
+ var count = 0;
+ while (count++ < 150) {
+ gc();
+ var start = window.performance.now();
+ create();
+ duration += window.performance.now() - start;
+ destroy();
+ }
+ windowProfileEnd(name + ' w GC');
+ window.console.log(`Iterations: ${count}; time: ${duration / count} ms / iteration`);
+
+ windowProfile(name + ' w/o GC');
+ duration = 0;
+ count = 0;
+ while (count++ < 150) {
+ var start = window.performance.now();
+ create();
+ duration += window.performance.now() - start;
+ destroy();
+ }
+ windowProfileEnd(name + ' w/o GC');
+ window.console.log(`Iterations: ${count}; time: ${duration / count} ms / iteration`);
+ };
+ }
+
+ function noop() {}
+
+ function createData(): TreeNode {
+ 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', '-'];
+ return buildTree(MAX_DEPTH, values, 0);
+ }
+
+ function ng2DestroyDom() {
+ app.initData = null;
+ lifeCycle.tick();
+ }
+
+ function ng2CreateDom() {
+ app.initData = createData();
+ lifeCycle.tick();
+ }
+
+ function initNg2() {
+ bootstrap(AppComponentWithStaticTree, createBindings())
+ .then((ref) => {
+ var injector = ref.injector;
+ lifeCycle = injector.get(LifeCycle);
+
+ app = ref.hostComponent;
+ bindAction('#ng2DestroyDom', ng2DestroyDom);
+ bindAction('#ng2CreateDom', ng2CreateDom);
+ bindAction('#ng2UpdateDomProfile', profile(ng2CreateDom, noop, 'ng2-update'));
+ bindAction('#ng2CreateDomProfile', profile(ng2CreateDom, ng2DestroyDom, 'ng2-create'));
+ });
+ }
+
+ function baselineDestroyDom() { baselineRootTreeComponent.update(null); }
+
+ function baselineCreateDom() { baselineRootTreeComponent.update(createData()); }
+
+ function initBaseline() {
+ var tree = DOM.createElement('tree');
+ DOM.appendChild(DOM.querySelector(document, 'baseline'), tree);
+ baselineRootTreeComponent = new BaselineAppComponent(tree, MAX_DEPTH);
+
+ bindAction('#baselineDestroyDom', baselineDestroyDom);
+ bindAction('#baselineCreateDom', baselineCreateDom);
+
+ bindAction('#baselineUpdateDomProfile', profile(baselineCreateDom, noop, 'baseline-update'));
+ bindAction('#baselineCreateDomProfile',
+ profile(baselineCreateDom, baselineDestroyDom, 'baseline-create'));
+ }
+
+ initNg2();
+ initBaseline();
+}
+
+class TreeNode {
+ value: string;
+ left: TreeNode;
+ right: TreeNode;
+ constructor(value, left, right) {
+ this.value = value;
+ this.left = left;
+ this.right = right;
+ }
+}
+
+function buildTree(maxDepth, values, curDepth) {
+ if (maxDepth === curDepth) return new TreeNode('', null, null);
+ return new TreeNode(values[curDepth], buildTree(maxDepth, values, curDepth + 1),
+ buildTree(maxDepth, values, curDepth + 1));
+}
+
+// http://jsperf.com/nextsibling-vs-childnodes
+
+class BaselineAppComponent {
+ tree: BaseLineTreeComponent = null;
+ constructor(public element, public depth: number) {}
+ update(value: TreeNode) {
+ if (value === null) {
+ this.tree = null;
+ DOM.clearNodes(this.element);
+ } else {
+ if (this.tree === null) {
+ this.tree = new BaseLineTreeComponent(this.element, this.depth);
+ }
+ this.tree.update(value);
+ }
+ }
+}
+
+var BASELINE_TREE_TEMPLATE = null;
+class BaseLineTreeComponent {
+ static getTemplate() {
+ if (BASELINE_TREE_TEMPLATE === null) {
+ BASELINE_TREE_TEMPLATE = DOM.createTemplate('_');
+ }
+ return BASELINE_TREE_TEMPLATE;
+ }
+
+ value: BaseLineInterpolation;
+ left: BaseLineTreeComponent;
+ right: BaseLineTreeComponent;
+ terminal: boolean;
+
+ constructor(public element, remainingDepth: number) {
+ var clone = DOM.firstChild(DOM.importIntoDoc(BaseLineTreeComponent.getTemplate().content));
+ DOM.appendChild(this.element, clone);
+ var child = clone.firstChild;
+ this.value = new BaseLineInterpolation(child);
+ this.terminal = remainingDepth === 0;
+ if (!this.terminal) {
+ child = DOM.nextSibling(child);
+ this.left = new BaseLineTreeComponent(child, remainingDepth - 1);
+ child = DOM.nextSibling(child);
+ this.right = new BaseLineTreeComponent(child, remainingDepth - 1);
+ }
+ }
+ update(value: TreeNode) {
+ this.value.update(value.value);
+ if (!this.terminal) {
+ this.left.update(value.left);
+ this.right.update(value.right);
+ }
+ }
+}
+
+class BaseLineInterpolation {
+ value: string;
+ textNode;
+ constructor(textNode) {
+ this.value = null;
+ this.textNode = textNode;
+ }
+ update(value: string) {
+ if (this.value !== value) {
+ this.value = value;
+ DOM.setText(this.textNode, value + ' ');
+ }
+ }
+}
+
+class StaticTreeComponentBase {
+ _value: TreeNode;
+ constructor() { this.data = null; }
+ set data(value: TreeNode) {
+ // TODO: We need an initial value as otherwise the getter for data.value will fail
+ // --> this should be already caught in change detection!
+ value = value !== null ? value : new TreeNode('', null, null);
+ this._value = value;
+ }
+ get data() { return this._value; }
+}
+
+@Component({selector: 'tree', properties: ['data']})
+@View({directives: [], template: '{{data.value}} '})
+class StaticTreeComponent0 extends StaticTreeComponentBase {
+}
+
+@Component({selector: 'tree', properties: ['data']})
+@View({
+ directives: [StaticTreeComponent0],
+ template:
+ ` {{data.value}} `
+})
+class StaticTreeComponent1 extends StaticTreeComponentBase {
+}
+
+@Component({selector: 'tree', properties: ['data']})
+@View({
+ directives: [StaticTreeComponent1],
+ template:
+ ` {{data.value}} `
+})
+class StaticTreeComponent2 extends StaticTreeComponentBase {
+ data: TreeNode;
+}
+
+@Component({selector: 'tree', properties: ['data']})
+@View({
+ directives: [StaticTreeComponent2],
+ template:
+ ` {{data.value}} `
+})
+class StaticTreeComponent3 extends StaticTreeComponentBase {
+}
+
+@Component({selector: 'tree', properties: ['data']})
+@View({
+ directives: [StaticTreeComponent3],
+ template:
+ ` {{data.value}} `
+})
+class StaticTreeComponent4 extends StaticTreeComponentBase {
+}
+
+@Component({selector: 'tree', properties: ['data']})
+@View({
+ directives: [StaticTreeComponent4],
+ template:
+ ` {{data.value}} `
+})
+class StaticTreeComponent5 extends StaticTreeComponentBase {
+}
+
+@Component({selector: 'tree', properties: ['data']})
+@View({
+ directives: [StaticTreeComponent5],
+ template:
+ ` {{data.value}} `
+})
+class StaticTreeComponent6 extends StaticTreeComponentBase {
+}
+
+@Component({selector: 'tree', properties: ['data']})
+@View({
+ directives: [StaticTreeComponent6],
+ template:
+ ` {{data.value}} `
+})
+class StaticTreeComponent7 extends StaticTreeComponentBase {
+}
+
+@Component({selector: 'tree', properties: ['data']})
+@View({
+ directives: [StaticTreeComponent7],
+ template:
+ ` {{data.value}} `
+})
+class StaticTreeComponent8 extends StaticTreeComponentBase {
+}
+
+@Component({selector: 'tree', properties: ['data']})
+@View({
+ directives: [StaticTreeComponent8],
+ template:
+ ` {{data.value}} `
+})
+class StaticTreeComponent9 extends StaticTreeComponentBase {
+}
+
+@Component({selector: 'app'})
+@View({
+ directives: [StaticTreeComponent9, NgIf],
+ template: ``
+})
+class AppComponentWithStaticTree {
+ initData: TreeNode;
+}
diff --git a/modules/benchmarks_external/e2e_test/static_tree.dart b/modules/benchmarks_external/e2e_test/static_tree.dart
new file mode 100644
index 0000000000..8868f131ba
--- /dev/null
+++ b/modules/benchmarks_external/e2e_test/static_tree.dart
@@ -0,0 +1,3 @@
+library benchmarks_external.e2e_test.static_tree_perf;
+
+main() {}
diff --git a/modules/benchmarks_external/e2e_test/static_tree.ts b/modules/benchmarks_external/e2e_test/static_tree.ts
new file mode 100644
index 0000000000..08c4adf316
--- /dev/null
+++ b/modules/benchmarks_external/e2e_test/static_tree.ts
@@ -0,0 +1,15 @@
+import {runClickBenchmark, verifyNoBrowserErrors} from 'angular2/src/test_lib/perf_util';
+
+describe('ng1.x tree benchmark', function() {
+
+ var URL = 'benchmarks_external/src/static_tree/tree_benchmark.html';
+
+ afterEach(verifyNoBrowserErrors);
+
+ it('should log the stats', function(done) {
+ runClickBenchmark(
+ {url: URL, buttons: ['#destroyDom', '#createDom'], id: 'ng1.static.tree', params: []})
+ .then(done, done.fail);
+ });
+
+});
diff --git a/modules/benchmarks_external/src/index.html b/modules/benchmarks_external/src/index.html
index 5899411802..d9c74f9c2e 100644
--- a/modules/benchmarks_external/src/index.html
+++ b/modules/benchmarks_external/src/index.html
@@ -8,6 +8,9 @@
Tree benchmark
+
+ Static tree benchmark
+
Polymer Tree benchmark
diff --git a/modules/benchmarks_external/src/static_tree/tree_benchmark.dart b/modules/benchmarks_external/src/static_tree/tree_benchmark.dart
new file mode 100644
index 0000000000..aa13adad95
--- /dev/null
+++ b/modules/benchmarks_external/src/static_tree/tree_benchmark.dart
@@ -0,0 +1,164 @@
+// static tree benchmark in AngularDart 1.x
+library static_tree_benchmark_ng10;
+
+import 'package:angular/angular.dart';
+import 'package:angular/application_factory.dart';
+import 'package:angular2/src/test_lib/benchmark_util.dart';
+
+setup() {
+ var m = new Module()
+ ..bind(CompilerConfig,
+ toValue: new CompilerConfig.withOptions(elementProbeEnabled: false))
+ ..bind(ScopeDigestTTL,
+ toFactory: () => new ScopeDigestTTL.value(15), inject: [])
+ ..bind(TreeComponent0)
+ ..bind(TreeComponent1)
+ ..bind(TreeComponent2)
+ ..bind(TreeComponent3)
+ ..bind(TreeComponent4)
+ ..bind(TreeComponent5)
+ ..bind(TreeComponent6)
+ ..bind(TreeComponent7)
+ ..bind(TreeComponent8)
+ ..bind(TreeComponent9);
+
+ final injector = applicationFactory().addModule(m).run();
+
+ return injector;
+}
+
+const MAX_DEPTH = 10;
+
+main() {
+ final injector = setup();
+ final zone = injector.get(VmTurnZone);
+ final rootScope = injector.get(Scope);
+ rootScope.context['initData'] = null;
+ var count = 0;
+
+ TreeNode createData() {
+ 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', '-'];
+ return buildTree(MAX_DEPTH, values, 0);
+ }
+
+ destroyDom() {
+ zone.run(() {
+ rootScope.context['initData'] = null;
+ });
+ }
+
+ createDom() {
+ zone.run(() {
+ rootScope.context['initData'] = createData();
+ });
+ }
+
+ bindAction('#destroyDom', destroyDom);
+ bindAction('#createDom', createDom);
+}
+
+@Component(
+ selector: 'tree0',
+ map: const {'data': '=>data'},
+ template: ' {{data.value}} '
+)
+class TreeComponent0 {
+ var data;
+}
+
+@Component(
+ selector: 'tree1',
+ map: const {'data': '=>data'},
+ template: ' {{data.value}} '
+)
+class TreeComponent1 {
+ var data;
+}
+
+@Component(
+ selector: 'tree2',
+ map: const {'data': '=>data'},
+ template: ' {{data.value}} '
+)
+class TreeComponent2 {
+ var data;
+}
+
+@Component(
+ selector: 'tree3',
+ map: const {'data': '=>data'},
+ template: ' {{data.value}} '
+)
+class TreeComponent3 {
+ var data;
+}
+
+@Component(
+ selector: 'tree4',
+ map: const {'data': '=>data'},
+ template: ' {{data.value}} '
+)
+class TreeComponent4 {
+ var data;
+}
+
+@Component(
+ selector: 'tree5',
+ map: const {'data': '=>data'},
+ template: ' {{data.value}} '
+)
+class TreeComponent5 {
+ var data;
+}
+
+@Component(
+ selector: 'tree6',
+ map: const {'data': '=>data'},
+ template: ' {{data.value}} '
+)
+class TreeComponent6 {
+ var data;
+}
+
+@Component(
+ selector: 'tree7',
+ map: const {'data': '=>data'},
+ template: ' {{data.value}} '
+)
+class TreeComponent7 {
+ var data;
+}
+
+@Component(
+ selector: 'tree8',
+ map: const {'data': '=>data'},
+ template: ' {{data.value}} '
+)
+class TreeComponent8 {
+ var data;
+}
+
+@Component(
+ selector: 'tree9',
+ map: const {'data': '=>data'},
+ template: ' {{data.value}} '
+)
+class TreeComponent9 {
+ var data;
+}
+
+buildTree(maxDepth, values, curDepth) {
+ if (maxDepth == curDepth) return new TreeNode('');
+ return new TreeNode(values[curDepth],
+ buildTree(maxDepth, values, curDepth + 1),
+ buildTree(maxDepth, values, curDepth + 1));
+}
+
+class TreeNode {
+ var value;
+ TreeNode left;
+ TreeNode right;
+ TreeNode([this.value, this.left, this.right]);
+}
diff --git a/modules/benchmarks_external/src/static_tree/tree_benchmark.html b/modules/benchmarks_external/src/static_tree/tree_benchmark.html
new file mode 100644
index 0000000000..8cb216a072
--- /dev/null
+++ b/modules/benchmarks_external/src/static_tree/tree_benchmark.html
@@ -0,0 +1,17 @@
+
+
+
+
+AngularJS/Dart 1.x static tree benchmark (depth 10)
+
+
+
+
+
+
+
+
+
+$SCRIPTS$
+
+
diff --git a/modules/benchmarks_external/src/static_tree/tree_benchmark.ts b/modules/benchmarks_external/src/static_tree/tree_benchmark.ts
new file mode 100644
index 0000000000..1507208a10
--- /dev/null
+++ b/modules/benchmarks_external/src/static_tree/tree_benchmark.ts
@@ -0,0 +1,70 @@
+// static tree benchmark in AngularJS 1.x
+import {getIntParameter, bindAction} from 'angular2/src/test_lib/benchmark_util';
+import angular = require("angular");
+
+const MAX_DEPTH = 10;
+
+export function main() {
+ angular.bootstrap(document.querySelector('.app'), ['app']);
+}
+
+function addTreeDirective(module, level: number) {
+ var template;
+ if (level <= 0) {
+ template = ` {{data.value}}`
+ } else {
+ template =
+ ` {{data.value}} `;
+ }
+ module.directive(`tree${level}`, function() { return {scope: {data: '='}, template: template}; });
+}
+
+var module = angular.module('app', []);
+for (var depth = 0; depth < MAX_DEPTH; depth++) {
+ addTreeDirective(module, depth);
+}
+module.config([
+ '$compileProvider',
+ function($compileProvider) { $compileProvider.debugInfoEnabled(false); }
+ ])
+ .run([
+ '$rootScope',
+ function($rootScope) {
+ var count = 0;
+ $rootScope.initData = null;
+
+ bindAction('#destroyDom', destroyDom);
+ bindAction('#createDom', createDom);
+
+ function createData(): TreeNode {
+ 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', '-'];
+ return buildTree(MAX_DEPTH, values, 0);
+ }
+
+ function destroyDom() {
+ $rootScope.$apply(function() { $rootScope.initData = null; });
+ }
+
+ function createDom() {
+ $rootScope.$apply(function() { $rootScope.initData = createData(); });
+ }
+ }
+ ]);
+
+class TreeNode {
+ value: string;
+ left: TreeNode;
+ right: TreeNode;
+ constructor(value, left, right) {
+ this.value = value;
+ this.left = left;
+ this.right = right;
+ }
+}
+
+function buildTree(maxDepth, values, curDepth) {
+ if (maxDepth === curDepth) return new TreeNode('', null, null);
+ return new TreeNode(values[curDepth], buildTree(maxDepth, values, curDepth + 1),
+ buildTree(maxDepth, values, curDepth + 1));
+}
diff --git a/tools/broccoli/trees/browser_tree.ts b/tools/broccoli/trees/browser_tree.ts
index 298d05f707..d90a37b6aa 100644
--- a/tools/broccoli/trees/browser_tree.ts
+++ b/tools/broccoli/trees/browser_tree.ts
@@ -28,6 +28,7 @@ const kServedPaths = [
'benchmarks/src/largetable',
'benchmarks/src/naive_infinite_scroll',
'benchmarks/src/tree',
+ 'benchmarks/src/static_tree',
// Relative (to /modules) paths to external benchmark directories
'benchmarks_external/src',
@@ -36,6 +37,7 @@ const kServedPaths = [
'benchmarks_external/src/naive_infinite_scroll',
'benchmarks_external/src/tree',
'benchmarks_external/src/tree/react',
+ 'benchmarks_external/src/static_tree',
// Relative (to /modules) paths to example directories
'examples/src/benchpress',