refactor(benchmarks): make tree benchmark work again

This commit is contained in:
Tobias Bosch
2016-08-26 12:27:24 -07:00
committed by Victor Berchet
parent 5ff14de1f3
commit 61002733bc
55 changed files with 620 additions and 403 deletions

View File

@ -0,0 +1,23 @@
<!doctype html>
<html>
<body>
<h2>Params</h2>
<form>
Elements:
<input type="number" name="elements" placeholder="elements" value="50">
<br>
<button>Apply</button>
</form>
<h2>Actions</h2>
<p>
<button id="compileWithBindings">CompileWithBindings</button>
<button id="compileNoBindings">CompileNoBindings</button>
</p>
<app></app>
$SCRIPTS$
</body>
</html>

View File

@ -0,0 +1,184 @@
import {bootstrap} from '@angular/platform-browser';
import {BrowserDomAdapter} from '@angular/platform-browser/src/browser/browser_adapter';
import {DOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {PromiseWrapper} from '@angular/facade/src/async';
import {ListWrapper, Map, MapWrapper} from '@angular/facade/src/collection';
import {DateWrapper, Type, print, isPresent} from '@angular/facade/src/lang';
import {
ComponentResolver,
Component,
Directive,
ViewContainerRef,
} from '@angular/core';
import {ViewMetadata} from '@angular/core/src/metadata/view';
import {CompilerConfig, DirectiveResolver} from '@angular/compiler';
import {getIntParameter, bindAction} from '@angular/testing/src/benchmark_util';
function _createBindings(): any[] {
var multiplyTemplatesBy = getIntParameter('elements');
return [
{
provide: DirectiveResolver,
useFactory: () => new MultiplyDirectiveResolver(
multiplyTemplatesBy,
[BenchmarkComponentNoBindings, BenchmarkComponentWithBindings]),
deps: []
},
// Use interpretative mode as Dart does not support JIT and
// we want to be able to compare the numbers between JS and Dart
{provide: CompilerConfig, useValue: new CompilerConfig({genDebugInfo: false, useJit: false, logBindingUpdate: false})}
];
}
export function main() {
BrowserDomAdapter.makeCurrent();
bootstrap(CompilerAppComponent, _createBindings())
.then((ref) => {
var app = ref.instance;
bindAction('#compileNoBindings',
measureWrapper(() => app.compileNoBindings(), 'No Bindings'));
bindAction('#compileWithBindings',
measureWrapper(() => app.compileWithBindings(), 'With Bindings'));
});
}
function measureWrapper(func, desc) {
return function() {
var begin = DateWrapper.now();
print(`[${desc}] Begin...`);
var onSuccess = function(_) {
var elapsedMs = DateWrapper.toMillis(DateWrapper.now()) - DateWrapper.toMillis(begin);
print(`[${desc}] ...done, took ${elapsedMs} ms`);
};
var onError = function(e) { DOM.logError(e); };
PromiseWrapper.then(func(), onSuccess, onError);
};
}
class MultiplyDirectiveResolver extends DirectiveResolver {
_multiplyBy: number;
_cache = new Map<Type, ViewMetadata>();
constructor(multiple: number, components: Type[]) {
super();
this._multiplyBy = multiple;
components.forEach(c => this._fillCache(c));
}
_fillCache(component: Type) {
var view = super.resolve(component);
var multipliedTemplates = ListWrapper.createFixedSize(this._multiplyBy);
for (var i = 0; i < this._multiplyBy; ++i) {
multipliedTemplates[i] = view.template;
}
this._cache.set(
component,
new ViewMetadata({template: multipliedTemplates.join(''), directives: view.directives}));
}
resolve(component: Type): ViewMetadata {
var result = this._cache.get(component);
return isPresent(result) ? result : super.resolve(component);
}
}
@Component({selector: 'app', directives: [], template: ``})
class CompilerAppComponent {
constructor(private _compiler: ComponentResolver) {}
compileNoBindings() {
this._compiler.clearCache();
return this._compiler.resolveComponent(BenchmarkComponentNoBindings);
}
compileWithBindings() {
this._compiler.clearCache();
return this._compiler.resolveComponent(BenchmarkComponentWithBindings);
}
}
@Directive({selector: '[dir0]', inputs: ['prop: attr0']})
class Dir0 {
prop: any;
}
@Directive({selector: '[dir1]', inputs: ['prop: attr1']})
class Dir1 {
prop: any;
constructor(dir0: Dir0) {}
}
@Directive({selector: '[dir2]', inputs: ['prop: attr2']})
class Dir2 {
prop: any;
constructor(dir1: Dir1) {}
}
@Directive({selector: '[dir3]', inputs: ['prop: attr3']})
class Dir3 {
prop: any;
constructor(dir2: Dir2) {}
}
@Directive({selector: '[dir4]', inputs: ['prop: attr4']})
class Dir4 {
prop: any;
constructor(dir3: Dir3) {}
}
@Component({
selector: 'cmp-nobind',
directives: [Dir0, Dir1, Dir2, Dir3, Dir4],
template: `
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
<div class="class0 class1 class2 class3 class4 " nodir0="" attr0="value0" nodir1="" attr1="value1" nodir2="" attr2="value2" nodir3="" attr3="value3" nodir4="" attr4="value4">
</div>
</div>
</div>
</div>
</div>`
})
class BenchmarkComponentNoBindings {
}
@Component({
selector: 'cmp-withbind',
directives: [Dir0, Dir1, Dir2, Dir3, Dir4],
template: `
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
<div class="class0 class1 class2 class3 class4 " dir0="" [attr0]="value0" dir1="" [attr1]="value1" dir2="" [attr2]="value2" dir3="" [attr3]="value3" dir4="" [attr4]="value4">
{{inter0}}{{inter1}}{{inter2}}{{inter3}}{{inter4}}
</div>
</div>
</div>
</div>
</div>`
})
class BenchmarkComponentWithBindings {
value0: any;
value1: any;
value2: any;
value3: any;
value4: any;
inter0: any;
inter1: any;
inter2: any;
inter3: any;
inter4: any;
}

View File

@ -0,0 +1,23 @@
<!doctype html>
<html>
<body>
<h2>Params</h2>
<form>
Selectors:
<input type="number" name="selectors" placeholder="selectors" value="10000">
<br>
<button>Apply</button>
</form>
<h2>Actions</h2>
<p>
<button id="parse">Selector.parse</button>
<button id="addSelectable">Selector.addSelectable</button>
<button id="match">Selector.match</button>
</p>
$SCRIPTS$
</body>
</html>

View File

@ -0,0 +1,82 @@
import {SelectorMatcher} from '@angular/compiler/src/selector';
import {CssSelector} from '@angular/compiler/src/selector';
import {StringWrapper, Math} from '@angular/facade/lang';
import {getIntParameter, bindAction} from '@angular/testing/src/benchmark_util';
import {BrowserDomAdapter} from '@angular/platform-browser/src/browser/browser_adapter';
export function main() {
BrowserDomAdapter.makeCurrent();
var count = getIntParameter('selectors');
var fixedMatcher;
var fixedSelectorStrings = [];
var fixedSelectors = [];
for (var i = 0; i < count; i++) {
fixedSelectorStrings.push(randomSelector());
}
for (var i = 0; i < count; i++) {
fixedSelectors.push(CssSelector.parse(fixedSelectorStrings[i]));
}
fixedMatcher = new SelectorMatcher();
for (var i = 0; i < count; i++) {
fixedMatcher.addSelectables(fixedSelectors[i], i);
}
function parse() {
var result = [];
for (var i = 0; i < count; i++) {
result.push(CssSelector.parse(fixedSelectorStrings[i]));
}
return result;
}
function addSelectable() {
var matcher = new SelectorMatcher();
for (var i = 0; i < count; i++) {
matcher.addSelectables(fixedSelectors[i], i);
}
return matcher;
}
function match() {
var matchCount = 0;
for (var i = 0; i < count; i++) {
fixedMatcher.match(fixedSelectors[i][0], (selector, selected) => { matchCount += selected; });
}
return matchCount;
}
bindAction('#parse', parse);
bindAction('#addSelectable', addSelectable);
bindAction('#match', match);
}
function randomSelector() {
var res = randomStr(5);
for (var i = 0; i < 3; i++) {
res += '.' + randomStr(5);
}
for (var i = 0; i < 3; i++) {
res += '[' + randomStr(3) + '=' + randomStr(6) + ']';
}
return res;
}
function randomStr(len) {
var s = '';
while (s.length < len) {
s += randomChar();
}
return s;
}
function randomChar() {
var n = randomNum(62);
if (n < 10) return n.toString(); // 1-10
if (n < 36) return StringWrapper.fromCharCode(n + 55); // A-Z
return StringWrapper.fromCharCode(n + 61); // a-z
}
function randomNum(max) {
return Math.floor(Math.random() * max);
}

View File

@ -0,0 +1,34 @@
<!doctype html>
<html>
<body>
<h2>Params</h2>
<form>
Size:
<input type="number" name="size" placeholder="size" value="100">
<br>
<button>Apply</button>
</form>
<h2>Benchmarks</h2>
<button id="reset">Reset</button>
<table>
<tr>
<td>Baseline (plain components)</td>
<td><button id="createPlainComponents">Run</button></td>
</tr>
<tr>
<td>Cost of decorators</td>
<td><button id="createComponentsWithDirectives">Run</button></td>
</tr>
<tr>
<td>Cost of dynamic components</td>
<td><button id="createDynamicComponents">Run</button></td>
</tr>
</table>
<app></app>
$SCRIPTS$
</body>
</html>

View File

@ -0,0 +1,118 @@
import {bootstrap} from '@angular/platform-browser';
import {Component, Directive, DynamicComponentLoader, ViewContainerRef} from '@angular/core';
import {NgIf, NgFor} from '@angular/common';
import {ApplicationRef} from '@angular/core/src/application_ref';
import {ListWrapper} from '@angular/facade/src/lang';
import {getIntParameter, bindAction} from '@angular/testing/src/benchmark_util';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
var testList = null;
export function main() {
var size = getIntParameter('size');
testList = ListWrapper.createFixedSize(size);
platformBrowserDynamic().bootstrapModule(AppModule)
.then((ref) => {
var injector = ref.injector;
var app: AppComponent = ref.instance;
var appRef = injector.get(ApplicationRef);
bindAction('#reset', function() {
app.reset();
appRef.tick();
});
// Baseline (plain components)
bindAction('#createPlainComponents', function() {
app.createPlainComponents();
appRef.tick();
});
// Components with decorators
bindAction('#createComponentsWithDirectives', function() {
app.createComponentsWithDirectives();
appRef.tick();
});
// Components with decorators
bindAction('#createDynamicComponents', function() {
app.createDynamicComponents();
appRef.tick();
});
});
}
@Component({selector: 'dummy', template: `<div></div>`})
class DummyComponent {
}
@Directive({selector: '[dummy-decorator]'})
class DummyDirective {
}
@Directive({selector: 'dynamic-dummy'})
class DynamicDummy {
constructor(loader: DynamicComponentLoader, location: ViewContainerRef) {
loader.loadNextToLocation(DummyComponent, location);
}
}
@Component({
selector: 'app',
directives: [NgIf, NgFor, DummyComponent, DummyDirective, DynamicDummy],
template: `
<div *ngIf="testingPlainComponents">
<dummy *ngFor="let i of list"></dummy>
</div>
<div *ngIf="testingWithDirectives">
<dummy dummy-decorator *ngFor="let i of list"></dummy>
</div>
<div *ngIf="testingDynamicComponents">
<dynamic-dummy *ngFor="let i of list"></dynamic-dummy>
</div>
`
})
class AppComponent {
list: any[];
testingPlainComponents: boolean;
testingWithDirectives: boolean;
testingDynamicComponents: boolean;
constructor() { this.reset(); }
reset(): void {
this.list = [];
this.testingPlainComponents = false;
this.testingWithDirectives = false;
this.testingDynamicComponents = false;
}
createPlainComponents(): void {
this.list = testList;
this.testingPlainComponents = true;
}
createComponentsWithDirectives(): void {
this.list = testList;
this.testingWithDirectives = true;
}
createDynamicComponents(): void {
this.list = testList;
this.testingDynamicComponents = true;
}
}
@NgModule({
imports: [BrowserModule],
bootstrap: [AppComponent]
})
class AppModule {
}

View File

@ -0,0 +1,25 @@
<!doctype html>
<html>
<body>
<h2>Params</h2>
<form>
Iterations:
<input type="number" name="iterations" placeholder="iterations" value="20000">
<br>
<button>Apply</button>
</form>
<h2>Actions</h2>
<p>
<button id="getByToken">getByToken</button>
<button id="getByKey">getByKey</button>
<button id="getChild">getChild</button>
<button id="instantiate">instantiate</button>
<button id="createVariety">createVariety</button>
<button id="createVarietyResolved">createVarietyResolved</button>
</div>
$SCRIPTS$
</body>
</html>

View File

@ -0,0 +1,118 @@
import {Injectable, ReflectiveInjector, ReflectiveKey} from '@angular/core';
import {reflector} from '@angular/core/src/reflection/reflection';
import {ReflectionCapabilities} from '@angular/core/src/reflection/reflection_capabilities';
import {getIntParameter, bindAction, microBenchmark} from '@angular/testing/src/benchmark_util';
import {BrowserDomAdapter} from '@angular/platform-browser/src/browser/browser_adapter';
var count = 0;
function setupReflector() {
reflector.reflectionCapabilities = new ReflectionCapabilities();
}
export function main() {
BrowserDomAdapter.makeCurrent();
var iterations = getIntParameter('iterations');
// This benchmark does not use bootstrap and needs to create a reflector
setupReflector();
var bindings = [A, B, C, D, E];
var injector = ReflectiveInjector.resolveAndCreate(bindings);
var D_KEY = ReflectiveKey.get(D);
var E_KEY = ReflectiveKey.get(E);
var childInjector = injector.resolveAndCreateChild([])
.resolveAndCreateChild([])
.resolveAndCreateChild([])
.resolveAndCreateChild([])
.resolveAndCreateChild([]);
var variousProviders = [A, {provide: B, useClass: C}, [D, [E]], {provide: F, useValue: 6}];
var variousProvidersResolved = ReflectiveInjector.resolve(variousProviders);
function getByToken() {
for (var i = 0; i < iterations; ++i) {
injector.get(D);
injector.get(E);
}
}
function getByKey() {
for (var i = 0; i < iterations; ++i) {
injector.get(D_KEY);
injector.get(E_KEY);
}
}
function getChild() {
for (var i = 0; i < iterations; ++i) {
childInjector.get(D);
childInjector.get(E);
}
}
function instantiate() {
for (var i = 0; i < iterations; ++i) {
var child = injector.resolveAndCreateChild([E]);
child.get(E);
}
}
/**
* Creates an injector with a variety of provider types.
*/
function createVariety() {
for (var i = 0; i < iterations; ++i) {
ReflectiveInjector.resolveAndCreate(variousProviders);
}
}
/**
* Same as [createVariety] but resolves providers ahead of time.
*/
function createVarietyResolved() {
for (var i = 0; i < iterations; ++i) {
ReflectiveInjector.fromResolvedProviders(variousProvidersResolved);
}
}
bindAction('#getByToken', () => microBenchmark('injectAvg', iterations, getByToken));
bindAction('#getByKey', () => microBenchmark('injectAvg', iterations, getByKey));
bindAction('#getChild', () => microBenchmark('injectAvg', iterations, getChild));
bindAction('#instantiate', () => microBenchmark('injectAvg', iterations, instantiate));
bindAction('#createVariety', () => microBenchmark('injectAvg', iterations, createVariety));
bindAction('#createVarietyResolved',
() => microBenchmark('injectAvg', iterations, createVarietyResolved));
}
@Injectable()
class A {
constructor() { count++; }
}
@Injectable()
class B {
constructor(a: A) { count++; }
}
@Injectable()
class C {
constructor(b: B) { count++; }
}
@Injectable()
class D {
constructor(c: C, b: B) { count++; }
}
@Injectable()
class E {
constructor(d: D, c: C) { count++; }
}
@Injectable()
class F {
constructor(e: E, d: D) { count++; }
}

View File

@ -0,0 +1,123 @@
<!doctype html>
<html>
<head>
<link rel="icon" href="data:;base64,=">
</head>
<body>
<h2>Params</h2>
<div>
<form>
<div>
Use Viewcache:
<label>
Yes<input type="radio" name="viewcache" placeholder="use viewcache" value="true" checked="checked">
</label>
<label>
No<input type="radio" name="viewcache" placeholder="use viewcache" value="false">
</label>
</div>
<div>
rows:
<input type="number" name="rows" value="100">
columns:
<input type="number" name="columns" value="20">
</div>
<div>
baseline (to be used in conjuction with Baseline:createDom &amp; Baseline:destroyDom buttons):
<input type="radio"
name="benchmarkType"
value="baseline"
id="baseline">
</div>
<div>
ngBind (not implemented):
<input type="radio"
name="benchmarkType"
value="ngBind"
id="ngBind">
</div>
<div>
ngBindOnce (not implemented):
<input type="radio"
name="benchmarkType"
value="ngBindOnce"
id="ngBindOnce">
</div>
<div>
interpolation:
<input type="radio"
name="benchmarkType"
value="interpolation"
id="interpolation"
checked>
</div>
<div>
attribute interpolation:
<input type="radio"
name="benchmarkType"
value="interpolationAttr"
id="interpolationAttr">
</div>
<div>
ngBind + fnInvocation (not implemented):
<input type="radio"
name="benchmarkType"
value="ngBindFn"
id="ngBindFn">
</div>
<div>
interpolation + fnInvocation:
<input type="radio"
name="benchmarkType"
value="interpolationFn"
id="interpolationFn">
</div>
<div>
ngBind + filter (not implemented):
<input type="radio"
name="benchmarkType"
value="ngBindFilter"
id="ngBindFilter">
</div>
<div>
interpolation + filter (not implemented):
<input type="radio"
name="benchmarkType"
value="interpolationFilter"
id="interpolationFilter">
</div>
<button>Apply</button>
</form>
</div>
<h2>Angular2 largetable benchmark</h2>
<p>
<button id="ng2DestroyDom">destroyDom</button>
<button id="ng2CreateDom">createDom</button>
<button id="ng2UpdateDomProfile">profile updateDom</button>
<button id="ng2CreateDomProfile">profile createDom</button>
</p>
<h2>Baseline largetable benchmark</h2>
<p>
<button id="baselineDestroyDom">destroyDom</button>
<button id="baselineCreateDom">createDom</button>
<button id="baselineUpdateDomProfile">profile updateDom</button>
<button id="baselineCreateDomProfile">profile createDom</button>
</p>
<div>
<app></app>
</div>
<div>
<baseline></baseline>
</div>
$SCRIPTS$
</body>
</html>

View File

@ -0,0 +1,267 @@
import {DOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {window, document, gc} from '@angular/facade/src/browser';
import {
getIntParameter,
getStringParameter,
bindAction,
windowProfile,
windowProfileEnd
} from '@angular/testing/src/benchmark_util';
import {bootstrap} from '@angular/platform-browser';
import {Component} from '@angular/core';
import {NgFor, NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';
import {ApplicationRef} from '@angular/core/src/application_ref';
import {BrowserDomAdapter} from '@angular/platform-browser/src/browser/browser_adapter';
import {ListWrapper} from '@angular/facade/src/collection';
import {Inject} from '@angular/core/src/di/decorators';
import {reflector} from '@angular/core/src/reflection/reflection';
export const BENCHMARK_TYPE = 'LargetableComponent.benchmarkType';
export const LARGETABLE_ROWS = 'LargetableComponent.rows';
export const LARGETABLE_COLS = 'LargetableComponent.cols';
function _createBindings() {
return [
{provide: BENCHMARK_TYPE, useValue: getStringParameter('benchmarkType')},
{provide: LARGETABLE_ROWS, useValue: getIntParameter('rows')},
{provide: LARGETABLE_COLS, useValue: getIntParameter('columns')},
];
}
var BASELINE_LARGETABLE_TEMPLATE;
function setupReflector() {
// TODO(kegluneq): Generate these.
reflector.registerGetters({
'benchmarktype': (o) => o.benchmarktype,
'switch': (o) => null,
'switchCase': (o) => o.switchCase
});
reflector.registerSetters({
'benchmarktype': (o, v) => o.benchmarktype = v,
'switch': (o, v) => null,
'switchCase': (o, v) => o.switchCase = v
});
}
export function main() {
BrowserDomAdapter.makeCurrent();
var totalRows = getIntParameter('rows');
var totalColumns = getIntParameter('columns');
BASELINE_LARGETABLE_TEMPLATE = DOM.createTemplate('<table></table>');
var app;
var appRef;
var baselineRootLargetableComponent;
function ng2DestroyDom() {
// TODO: We need an initial value as otherwise the getter for data.value will fail
// --> this should be already caught in change detection!
app.data = null;
app.benchmarkType = 'none';
appRef.tick();
}
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 ng2CreateDom() {
var data = ListWrapper.createFixedSize(totalRows);
for (var i = 0; i < totalRows; i++) {
data[i] = ListWrapper.createFixedSize(totalColumns);
for (var j = 0; j < totalColumns; j++) {
data[i][j] = new CellData(i, j);
}
}
app.data = data;
app.benchmarkType = getStringParameter('benchmarkType');
appRef.tick();
}
function noop() {}
function initNg2() {
bootstrap(AppComponent, _createBindings())
.then((ref) => {
var injector = ref.injector;
app = ref.instance;
appRef = injector.get(ApplicationRef);
bindAction('#ng2DestroyDom', ng2DestroyDom);
bindAction('#ng2CreateDom', ng2CreateDom);
bindAction('#ng2UpdateDomProfile', profile(ng2CreateDom, noop, 'ng2-update'));
bindAction('#ng2CreateDomProfile', profile(ng2CreateDom, ng2DestroyDom, 'ng2-create'));
});
setupReflector();
}
function baselineDestroyDom() { baselineRootLargetableComponent.update(buildTable(0, 0)); }
function baselineCreateDom() {
baselineRootLargetableComponent.update(buildTable(totalRows, totalColumns));
}
function initBaseline() {
baselineRootLargetableComponent = new BaseLineLargetableComponent(
DOM.querySelector(document, 'baseline'), getStringParameter('benchmarkType'),
getIntParameter('rows'), getIntParameter('columns'));
bindAction('#baselineDestroyDom', baselineDestroyDom);
bindAction('#baselineCreateDom', baselineCreateDom);
bindAction('#baselineUpdateDomProfile', profile(baselineCreateDom, noop, 'baseline-update'));
bindAction('#baselineCreateDomProfile',
profile(baselineCreateDom, baselineDestroyDom, 'baseline-create'));
}
initNg2();
initBaseline();
}
function buildTable(rows, columns) {
var tbody = DOM.createElement('tbody');
var template = DOM.createElement('span');
var i, j, row, cell;
DOM.appendChild(template, DOM.createElement('span'));
DOM.appendChild(template, DOM.createTextNode(':'));
DOM.appendChild(template, DOM.createElement('span'));
DOM.appendChild(template, DOM.createTextNode('|'));
for (i = 0; i < rows; i++) {
row = DOM.createElement('div');
DOM.appendChild(tbody, row);
for (j = 0; j < columns; j++) {
cell = DOM.clone(template);
DOM.appendChild(row, cell);
DOM.setText(cell.childNodes[0], i.toString());
DOM.setText(cell.childNodes[2], j.toString());
}
}
return tbody;
}
class BaseLineLargetableComponent {
element;
table;
benchmarkType: string;
rows: number;
columns: number;
constructor(element, benchmarkType, rows: number, columns: number) {
this.element = element;
this.benchmarkType = benchmarkType;
this.rows = rows;
this.columns = columns;
this.table = DOM.clone(BASELINE_LARGETABLE_TEMPLATE.content.firstChild);
var shadowRoot = DOM.createShadowRoot(this.element);
DOM.appendChild(shadowRoot, this.table);
}
update(tbody) {
var oldBody = DOM.querySelector(this.table, 'tbody');
if (oldBody != null) {
DOM.replaceChild(this.table, tbody, oldBody);
} else {
DOM.appendChild(this.table, tbody);
}
}
}
class CellData {
i: number;
j: number;
constructor(i, j) {
this.i = i;
this.j = j;
}
jFn() { return this.j; }
iFn() { return this.i; }
}
@Component({
selector: 'largetable',
inputs: ['data', 'benchmarkType'],
directives: [NgFor, NgSwitch, NgSwitchCase, NgSwitchDefault],
template: `
<table [ngSwitch]="benchmarkType">
<tbody template="ngSwitchCase 'interpolation'">
<tr template="ngFor let row of data">
<td template="ngFor let column of row">
{{column.i}}:{{column.j}}|
</td>
</tr>
</tbody>
<tbody template="ngSwitchCase 'interpolationAttr'">
<tr template="ngFor let row of data">
<td template="ngFor let column of row" attr.i="{{column.i}}" attr.j="{{column.j}}">
i,j attrs
</td>
</tr>
</tbody>
<tbody template="ngSwitchCase 'interpolationFn'">
<tr template="ngFor let row of data">
<td template="ngFor let column of row">
{{column.iFn()}}:{{column.jFn()}}|
</td>
</tr>
</tbody>
<tbody template="ngSwitchDefault">
<tr>
<td>
<em>{{benchmarkType}} not yet implemented</em>
</td>
</tr>
</tbody>
</table>`
})
class LargetableComponent {
data;
benchmarkType: string;
rows: number;
columns: number;
constructor(@Inject(BENCHMARK_TYPE) benchmarkType, @Inject(LARGETABLE_ROWS) rows,
@Inject(LARGETABLE_COLS) columns) {
this.benchmarkType = benchmarkType;
this.rows = rows;
this.columns = columns;
}
}
@Component({
selector: 'app',
directives: [LargetableComponent],
template: `<largetable [data]='data' [benchmarkType]='benchmarkType'></largetable>`
})
class AppComponent {
data;
benchmarkType: string;
}

View File

@ -0,0 +1,86 @@
import {isPresent} from '@angular/facade/src/lang';
import {getIntParameter, bindAction} from '@angular/testing/src/benchmark_util';
import {TimerWrapper} from '@angular/facade/src/async';
import {ScrollAreaComponent} from './scroll_area';
import {NgIf, NgFor} from '@angular/common';
import {DOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {document} from '@angular/facade/src/browser';
import {Component, Directive} from '@angular/core';
@Component({
selector: 'scroll-app',
directives: [ScrollAreaComponent, NgIf, NgFor],
template: `
<div>
<div style="display: flex">
<scroll-area id="testArea"></scroll-area>
</div>
<div template="ngIf scrollAreas.length > 0">
<p>Following tables are only here to add weight to the UI:</p>
<scroll-area template="ngFor let scrollArea of scrollAreas"></scroll-area>
</div>
</div>`
})
export class App {
scrollAreas: number[];
iterationCount: number;
scrollIncrement: number;
constructor() {
var appSize = getIntParameter('appSize');
this.iterationCount = getIntParameter('iterationCount');
this.scrollIncrement = getIntParameter('scrollIncrement');
appSize = appSize > 1 ? appSize - 1 : 0; // draw at least one table
this.scrollAreas = [];
for (var i = 0; i < appSize; i++) {
this.scrollAreas.push(i);
}
bindAction('#run-btn', () => { this.runBenchmark(); });
bindAction('#reset-btn', () => {
this._getScrollDiv().scrollTop = 0;
var existingMarker = this._locateFinishedMarker();
if (isPresent(existingMarker)) {
DOM.removeChild(document.body, existingMarker);
}
});
}
runBenchmark() {
var scrollDiv = this._getScrollDiv();
var n: number = this.iterationCount;
var scheduleScroll;
scheduleScroll = () => {
TimerWrapper.setTimeout(() => {
scrollDiv.scrollTop += this.scrollIncrement;
n--;
if (n > 0) {
scheduleScroll();
} else {
this._scheduleFinishedMarker();
}
}, 0);
};
scheduleScroll();
}
// Puts a marker indicating that the test is finished.
_scheduleFinishedMarker() {
var existingMarker = this._locateFinishedMarker();
if (isPresent(existingMarker)) {
// Nothing to do, the marker is already there
return;
}
TimerWrapper.setTimeout(() => {
var finishedDiv = DOM.createElement('div');
finishedDiv.id = 'done';
DOM.setInnerHTML(finishedDiv, 'Finished');
DOM.appendChild(document.body, finishedDiv);
}, 0);
}
_locateFinishedMarker() { return DOM.querySelector(document.body, '#done'); }
_getScrollDiv() { return DOM.query('body /deep/ #scrollDiv'); }
}

View File

@ -0,0 +1,129 @@
import {ListWrapper, Map} from '@angular/facade/src/collection';
import {Company, Opportunity, Offering, Account, CustomDate, STATUS_LIST} from './common';
import {NgFor} from '@angular/common';
import {Component, Directive} from '@angular/core';
export class HasStyle {
cellWidth: number;
constructor() {}
set width(w: number) { this.cellWidth = w; }
}
@Component({
selector: 'company-name',
inputs: ['width: cell-width', 'company'],
directives: [],
template: `<div [style.width.px]="cellWidth">{{company.name}}</div>`
})
export class CompanyNameComponent extends HasStyle {
company: Company;
}
@Component({
selector: 'opportunity-name',
inputs: ['width: cell-width', 'opportunity'],
directives: [],
template: `<div [style.width.px]="cellWidth">{{opportunity.name}}</div>`
})
export class OpportunityNameComponent extends HasStyle {
opportunity: Opportunity;
}
@Component({
selector: 'offering-name',
inputs: ['width: cell-width', 'offering'],
directives: [],
template: `<div [style.width.px]="cellWidth">{{offering.name}}</div>`
})
export class OfferingNameComponent extends HasStyle {
offering: Offering;
}
export class Stage {
name: string;
isDisabled: boolean;
backgroundColor: string;
apply: Function;
}
@Component({
selector: 'stage-buttons',
inputs: ['width: cell-width', 'offering'],
directives: [NgFor],
template: `
<div [style.width.px]="cellWidth">
<button template="ngFor let stage of stages"
[disabled]="stage.isDisabled"
[style.background-color]="stage.backgroundColor"
on-click="setStage(stage)">
{{stage.name}}
</button>
</div>`
})
export class StageButtonsComponent extends HasStyle {
_offering: Offering;
stages: Stage[];
get offering(): Offering { return this._offering; }
set offering(offering: Offering) {
this._offering = offering;
this._computeStageButtons();
}
setStage(stage: Stage) {
this._offering.status = stage.name;
this._computeStageButtons();
}
_computeStageButtons() {
var disabled = true;
this.stages = ListWrapper.clone(STATUS_LIST.map((status) => {
var isCurrent = this._offering.status == status;
var stage = new Stage();
stage.name = status;
stage.isDisabled = disabled;
stage.backgroundColor = disabled ? '#DDD' : isCurrent ? '#DDF' : '#FDD';
if (isCurrent) {
disabled = false;
}
return stage;
}));
}
}
@Component({
selector: 'account-cell',
inputs: ['width: cell-width', 'account'],
directives: [],
template: `
<div [style.width.px]="cellWidth">
<a href="/account/{{account.accountId}}">
{{account.accountId}}
</a>
</div>`
})
export class AccountCellComponent extends HasStyle {
account: Account;
}
@Component({
selector: 'formatted-cell',
inputs: ['width: cell-width', 'value'],
directives: [],
template: `<div [style.width.px]="cellWidth">{{formattedValue}}</div>`
})
export class FormattedCellComponent extends HasStyle {
formattedValue: string;
set value(value) {
if (value instanceof CustomDate) {
this.formattedValue = `${value.month}/${value.day}/${value.year}`;
} else {
this.formattedValue = value.toString();
}
}
}

View File

@ -0,0 +1,159 @@
import {Math} from '@angular/facade/src/math';
import {StringWrapper} from '@angular/facade/src/lang';
import {ListWrapper, Map, MapWrapper} from '@angular/facade/src/collection';
export var ITEMS = 1000;
export var ITEM_HEIGHT = 40;
export var VISIBLE_ITEMS = 17;
export var HEIGHT = ITEMS * ITEM_HEIGHT;
export var VIEW_PORT_HEIGHT = ITEM_HEIGHT * VISIBLE_ITEMS;
export var COMPANY_NAME_WIDTH = 100;
export var OPPORTUNITY_NAME_WIDTH = 100;
export var OFFERING_NAME_WIDTH = 100;
export var ACCOUNT_CELL_WIDTH = 50;
export var BASE_POINTS_WIDTH = 50;
export var KICKER_POINTS_WIDTH = 50;
export var STAGE_BUTTONS_WIDTH = 220;
export var BUNDLES_WIDTH = 120;
export var DUE_DATE_WIDTH = 100;
export var END_DATE_WIDTH = 100;
export var AAT_STATUS_WIDTH = 100;
export var ROW_WIDTH = COMPANY_NAME_WIDTH + OPPORTUNITY_NAME_WIDTH + OFFERING_NAME_WIDTH +
ACCOUNT_CELL_WIDTH + BASE_POINTS_WIDTH + KICKER_POINTS_WIDTH +
STAGE_BUTTONS_WIDTH + BUNDLES_WIDTH + DUE_DATE_WIDTH + END_DATE_WIDTH +
AAT_STATUS_WIDTH;
export var STATUS_LIST = ['Planned', 'Pitched', 'Won', 'Lost'];
export var AAT_STATUS_LIST = ['Active', 'Passive', 'Abandoned'];
// Imitate Streamy entities.
// Just a non-trivial object. Nothing fancy or correct.
export class CustomDate {
year: number;
month: number;
day: number;
constructor(y: number, m: number, d: number) {
this.year = y;
this.month = m;
this.day = d;
}
addDays(days: number): CustomDate {
var newDay = this.day + days;
var newMonth = this.month + Math.floor(newDay / 30);
newDay = newDay % 30;
var newYear = this.year + Math.floor(newMonth / 12);
return new CustomDate(newYear, newMonth, newDay);
}
static now(): CustomDate { return new CustomDate(2014, 1, 28); }
}
export class RawEntity {
_data: Map<any, any>;
constructor() { this._data = new Map(); }
get(key: string) {
if (key.indexOf('.') == -1) {
return this._data[key];
}
var pieces = key.split('.');
var last = ListWrapper.last(pieces);
pieces.length = pieces.length - 1;
var target = this._resolve(pieces, this);
if (target == null) {
return null;
}
return target[last];
}
set(key: string, value) {
if (key.indexOf('.') == -1) {
this._data[key] = value;
return;
}
var pieces = key.split('.');
var last = ListWrapper.last(pieces);
pieces.length = pieces.length - 1;
var target = this._resolve(pieces, this);
target[last] = value;
}
remove(key: string) {
if (!StringWrapper.contains(key, '.')) {
return this._data.delete(key);
}
var pieces = key.split('.');
var last = ListWrapper.last(pieces);
pieces.length = pieces.length - 1;
var target = this._resolve(pieces, this);
return target.remove(last);
}
_resolve(pieces, start) {
var cur = start;
for (var i = 0; i < pieces.length; i++) {
cur = cur[pieces[i]];
if (cur == null) {
return null;
}
}
return cur;
}
}
export class Company extends RawEntity {
get name(): string { return this.get('name'); }
set name(val: string) { this.set('name', val); }
}
export class Offering extends RawEntity {
get name(): string { return this.get('name'); }
set name(val: string) { this.set('name', val); }
get company(): Company { return this.get('company'); }
set company(val: Company) { this.set('company', val); }
get opportunity(): Opportunity { return this.get('opportunity'); }
set opportunity(val: Opportunity) { this.set('opportunity', val); }
get account(): Account { return this.get('account'); }
set account(val: Account) { this.set('account', val); }
get basePoints(): number { return this.get('basePoints'); }
set basePoints(val: number) { this.set('basePoints', val); }
get kickerPoints(): number { return this.get('kickerPoints'); }
set kickerPoints(val: number) { this.set('kickerPoints', val); }
get status(): string { return this.get('status'); }
set status(val: string) { this.set('status', val); }
get bundles(): string { return this.get('bundles'); }
set bundles(val: string) { this.set('bundles', val); }
get dueDate(): CustomDate { return this.get('dueDate'); }
set dueDate(val: CustomDate) { this.set('dueDate', val); }
get endDate(): CustomDate { return this.get('endDate'); }
set endDate(val: CustomDate) { this.set('endDate', val); }
get aatStatus(): string { return this.get('aatStatus'); }
set aatStatus(val: string) { this.set('aatStatus', val); }
}
export class Opportunity extends RawEntity {
get name(): string { return this.get('name'); }
set name(val: string) { this.set('name', val); }
}
export class Account extends RawEntity {
get accountId(): number { return this.get('accountId'); }
set accountId(val: number) { this.set('accountId', val); }
}

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<title>AngularDart Scrolling Benchmark</title>
</head>
<body>
<form>
App size: <input type="text" name="appSize" value="1"><br>
Iteration count: <input type="text" name="iterationCount" value="1"><br>
Scroll increment: <input type="text" name="scrollIncrement" value="1"><br>
</form>
<div>
<button id="run-btn">Run</button>
<button id="reset-btn">Reset</button>
</div>
<scroll-app>Loading...</scroll-app>
$SCRIPTS$
</body>
</html>

View File

@ -0,0 +1,18 @@
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {
NgModule
} from '@angular/core';
import {App} from './app';
@NgModule({
imports: [BrowserModule],
bootstrap: [App]
})
class AppModule {
}
export function main() {
platformBrowserDynamic().bootstrapModule(AppModule);
}

View File

@ -0,0 +1,94 @@
import {StringWrapper} from '@angular/facade/src/lang';
import {
CustomDate,
Offering,
Company,
Opportunity,
Account,
STATUS_LIST,
AAT_STATUS_LIST
} from './common';
export function generateOfferings(count: number): Offering[] {
var res = [];
for (var i = 0; i < count; i++) {
res.push(generateOffering(i));
}
return res;
}
export function generateOffering(seed: number): Offering {
var res = new Offering();
res.name = generateName(seed++);
res.company = generateCompany(seed++);
res.opportunity = generateOpportunity(seed++);
res.account = generateAccount(seed++);
res.basePoints = seed % 10;
res.kickerPoints = seed % 4;
res.status = STATUS_LIST[seed % STATUS_LIST.length];
res.bundles = randomString(seed++);
res.dueDate = randomDate(seed++);
res.endDate = randomDate(seed++, res.dueDate);
res.aatStatus = AAT_STATUS_LIST[seed % AAT_STATUS_LIST.length];
return res;
}
export function generateCompany(seed: number): Company {
var res = new Company();
res.name = generateName(seed);
return res;
}
export function generateOpportunity(seed: number): Opportunity {
var res = new Opportunity();
res.name = generateName(seed);
return res;
}
export function generateAccount(seed: number): Account {
var res = new Account();
res.accountId = seed;
return res;
}
var names = [
'Foo',
'Bar',
'Baz',
'Qux',
'Quux',
'Garply',
'Waldo',
'Fred',
'Plugh',
'Xyzzy',
'Thud',
'Cruft',
'Stuff'
];
function generateName(seed: number): string {
return names[seed % names.length];
}
var offsets = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
function randomDate(seed: number, minDate: CustomDate = null): CustomDate {
if (minDate == null) {
minDate = CustomDate.now();
}
return minDate.addDays(offsets[seed % offsets.length]);
}
var stringLengths = [5, 7, 9, 11, 13];
var charCodeOffsets = [0, 1, 2, 3, 4, 5, 6, 7, 8];
function randomString(seed: number): string {
var len = stringLengths[seed % 5];
var str = '';
for (var i = 0; i < len; i++) {
str += StringWrapper.fromCharCode(97 + charCodeOffsets[seed % 9] + i);
}
return str;
}

View File

@ -0,0 +1,77 @@
import {ListWrapper} from '@angular/facade/src/collection';
import {Math} from '@angular/facade/src/math';
import {Component, Directive} from '@angular/core';
import {
Offering,
ITEMS,
ITEM_HEIGHT,
VISIBLE_ITEMS,
VIEW_PORT_HEIGHT,
ROW_WIDTH,
HEIGHT
} from './common';
import {generateOfferings} from './random_data';
import {ScrollItemComponent} from './scroll_item';
import {NgFor} from '@angular/common';
@Component({
selector: 'scroll-area',
directives: [ScrollItemComponent, NgFor],
template: `
<div>
<div id="scrollDiv"
[style.height.px]="viewPortHeight"
style="width: 1000px; border: 1px solid #000; overflow: scroll"
on-scroll="onScroll($event)">
<div id="padding"></div>
<div id="inner">
<scroll-item
template="ngFor let item of visibleItems"
[offering]="item">
</scroll-item>
</div>
</div>
</div>`
})
export class ScrollAreaComponent {
_fullList: Offering[];
visibleItems: Offering[];
viewPortHeight: number;
paddingDiv;
innerDiv;
constructor() {
this._fullList = generateOfferings(ITEMS);
this.visibleItems = [];
this.viewPortHeight = VIEW_PORT_HEIGHT;
this.onScroll(null);
}
onScroll(evt) {
var scrollTop = 0;
if (evt != null) {
var scrollDiv = evt.target;
if (this.paddingDiv == null) {
this.paddingDiv = scrollDiv.querySelector('#padding');
}
if (this.innerDiv == null) {
this.innerDiv = scrollDiv.querySelector('#inner');
this.innerDiv.style.setProperty('width', `${ROW_WIDTH}px`);
}
scrollTop = scrollDiv.scrollTop;
}
var iStart = Math.floor(scrollTop / ITEM_HEIGHT);
var iEnd = Math.min(iStart + VISIBLE_ITEMS + 1, this._fullList.length);
var padding = iStart * ITEM_HEIGHT;
if (this.innerDiv != null) {
this.innerDiv.style.setProperty('height', `${HEIGHT - padding}px`);
}
if (this.paddingDiv != null) {
this.paddingDiv.style.setProperty('height', `${padding}px`);
}
this.visibleItems = ListWrapper.slice(this._fullList, iStart, iEnd);
}
}

View File

@ -0,0 +1,97 @@
import {
CompanyNameComponent,
OpportunityNameComponent,
OfferingNameComponent,
StageButtonsComponent,
AccountCellComponent,
FormattedCellComponent
} from './cells';
import {Component, Directive} from '@angular/core';
import {
Offering,
ITEM_HEIGHT,
COMPANY_NAME_WIDTH,
OPPORTUNITY_NAME_WIDTH,
OFFERING_NAME_WIDTH,
ACCOUNT_CELL_WIDTH,
BASE_POINTS_WIDTH,
KICKER_POINTS_WIDTH,
STAGE_BUTTONS_WIDTH,
BUNDLES_WIDTH,
DUE_DATE_WIDTH,
END_DATE_WIDTH,
AAT_STATUS_WIDTH
} from './common';
@Component({
selector: 'scroll-item',
inputs: ['offering'],
directives: [
CompanyNameComponent,
OpportunityNameComponent,
OfferingNameComponent,
StageButtonsComponent,
AccountCellComponent,
FormattedCellComponent
],
template: `
<div class="row"
[style.height.px]="itemHeight"
[style.line-height.px]="itemHeight"
style="font-size: 18px; display: flex; justify-content: space-between;">
<company-name [company]="offering.company"
[cell-width]="companyNameWidth">
</company-name>
<opportunity-name [opportunity]="offering.opportunity"
[cell-width]="opportunityNameWidth">
</opportunity-name>
<offering-name [offering]="offering"
[cell-width]="offeringNameWidth">
</offering-name>
<account-cell [account]="offering.account"
[cell-width]="accountCellWidth">
</account-cell>
<formatted-cell [value]="offering.basePoints"
[cell-width]="basePointsWidth">
</formatted-cell>
<formatted-cell [value]="offering.kickerPoints"
[cell-width]="kickerPointsWidth">
</formatted-cell>
<stage-buttons [offering]="offering"
[cell-width]="stageButtonsWidth">
</stage-buttons>
<formatted-cell [value]="offering.bundles"
[cell-width]="bundlesWidth">
</formatted-cell>
<formatted-cell [value]="offering.dueDate"
[cell-width]="dueDateWidth">
</formatted-cell>
<formatted-cell [value]="offering.endDate"
[cell-width]="endDateWidth">
</formatted-cell>
<formatted-cell [value]="offering.aatStatus"
[cell-width]="aatStatusWidth">
</formatted-cell>
</div>`
})
export class ScrollItemComponent {
offering: Offering;
itemHeight: number;
constructor() { this.itemHeight = ITEM_HEIGHT; }
get companyNameWidth() { return COMPANY_NAME_WIDTH; }
get opportunityNameWidth() { return OPPORTUNITY_NAME_WIDTH; }
get offeringNameWidth() { return OFFERING_NAME_WIDTH; }
get accountCellWidth() { return ACCOUNT_CELL_WIDTH; }
get basePointsWidth() { return BASE_POINTS_WIDTH; }
get kickerPointsWidth() { return KICKER_POINTS_WIDTH; }
get stageButtonsWidth() { return STAGE_BUTTONS_WIDTH; }
get bundlesWidth() { return BUNDLES_WIDTH; }
get dueDateWidth() { return DUE_DATE_WIDTH; }
get endDateWidth() { return END_DATE_WIDTH; }
get aatStatusWidth() { return AAT_STATUS_WIDTH; }
}

View File

@ -0,0 +1,13 @@
<!doctype html>
<html>
<body>
<h2>Angular2 page load benchmark</h2>
<div>
<app></app>
</div>
$SCRIPTS$
</body>
</html>

View File

@ -0,0 +1,19 @@
import {Component, NgModule} from 'angular2/core';
import {BrowserModule} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
@Component({selector: 'app', template: '<h1>Page Load Time</h1>'})
class App {
}
@NgModule({
imports: [BrowserModule],
bootstrap: [App],
})
class AppModule {
}
platformBrowserDynamic().bootstrapModule(App).then(() => {
(<any>window).loadTime = Date.now() - performance.timing.navigationStart;
(<any>window).someConstant = 1234567890;
});

View File

@ -0,0 +1,45 @@
<!doctype html>
<html>
<body>
<h2>Params</h2>
<form>
<br>
Use Viewcache:
<label>
Yes<input type="radio" name="viewcache" placeholder="use viewcache" value="true" checked="checked">
</label>
<label>
No<input type="radio" name="viewcache" placeholder="use viewcache" value="false">
</label>
<br>
<button>Apply</button>
</form>
<h2>Angular2 static tree benchmark (depth 10)</h2>
<p>
<button id="ng2DestroyDom">destroyDom</button>
<button id="ng2CreateDom">createDom</button>
<button id="ng2UpdateDomProfile">profile updateDom</button>
<button id="ng2CreateDomProfile">profile createDom</button>
</p>
<h2>Baseline static tree benchmark (depth 10)</h2>
<p>
<button id="baselineDestroyDom">destroyDom</button>
<button id="baselineCreateDom">createDom</button>
<button id="baselineUpdateDomProfile">profile updateDom</button>
<button id="baselineCreateDomProfile">profile createDom</button>
</p>
<div>
<app></app>
</div>
<div>
<baseline></baseline>
</div>
$SCRIPTS$
</body>
</html>

View File

@ -0,0 +1,321 @@
import {NgIf} from '@angular/common';
import {Component, NgModule} from '@angular/core';
import {ApplicationRef} from '@angular/core/src/application_ref';
import {reflector} from '@angular/core/src/reflection/reflection';
import {ReflectionCapabilities} from '@angular/core/src/reflection/reflection_capabilities';
import {DOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {window, document, gc} from '@angular/facade/src/browser';
import {
getIntParameter,
getStringParameter,
bindAction,
windowProfile,
windowProfileEnd
} from '@angular/testing/src/benchmark_util';
import {BrowserDomAdapter} from '@angular/platform-browser/src/browser/browser_adapter';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {BrowserModule} from '@angular/platform-browser';
function createBindings(): any[] {
return [];
}
function setupReflector() {
reflector.reflectionCapabilities = new ReflectionCapabilities();
}
const MAX_DEPTH = 10;
export function main() {
BrowserDomAdapter.makeCurrent();
setupReflector();
var app;
var appRef;
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;
appRef.tick();
}
function ng2CreateDom() {
app.initData = createData();
appRef.tick();
}
@NgModule({
imports: [BrowserModule],
bootstrap: [AppComponentWithStaticTree],
providers: createBindings()
})
class AppModule {
}
function initNg2() {
platformBrowserDynamic().bootstrapModule(AppModule)
.then((ref) => {
var injector = ref.injector;
appRef = injector.get(ApplicationRef);
app = ref.instance;
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('<span>_<tree></tree><tree></tree></span>');
}
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', inputs: ['data'], directives: [], template: '<span>{{data.value}} </span>'})
class StaticTreeComponent0 extends StaticTreeComponentBase {
}
@Component({
selector: 'tree',
inputs: ['data'],
directives: [StaticTreeComponent0],
template: `<span> {{data.value}} <tree [data]='data.right'></tree><tree [data]='data.left'></tree></span>`
})
class StaticTreeComponent1 extends StaticTreeComponentBase {
}
@Component({
selector: 'tree',
inputs: ['data'],
directives: [StaticTreeComponent1],
template: `<span> {{data.value}} <tree [data]='data.right'></tree><tree [data]='data.left'></tree></span>`
})
class StaticTreeComponent2 extends StaticTreeComponentBase {
data: TreeNode;
}
@Component({
selector: 'tree',
inputs: ['data'],
directives: [StaticTreeComponent2],
template: `<span> {{data.value}} <tree [data]='data.right'></tree><tree [data]='data.left'></tree></span>`
})
class StaticTreeComponent3 extends StaticTreeComponentBase {
}
@Component({
selector: 'tree',
inputs: ['data'],
directives: [StaticTreeComponent3],
template: `<span> {{data.value}} <tree [data]='data.right'></tree><tree [data]='data.left'></tree></span>`
})
class StaticTreeComponent4 extends StaticTreeComponentBase {
}
@Component({
selector: 'tree',
inputs: ['data'],
directives: [StaticTreeComponent4],
template: `<span> {{data.value}} <tree [data]='data.right'></tree><tree [data]='data.left'></tree></span>`
})
class StaticTreeComponent5 extends StaticTreeComponentBase {
}
@Component({
selector: 'tree',
inputs: ['data'],
directives: [StaticTreeComponent5],
template: `<span> {{data.value}} <tree [data]='data.right'></tree><tree [data]='data.left'></tree></span>`
})
class StaticTreeComponent6 extends StaticTreeComponentBase {
}
@Component({
selector: 'tree',
inputs: ['data'],
directives: [StaticTreeComponent6],
template: `<span> {{data.value}} <tree [data]='data.right'></tree><tree [data]='data.left'></tree></span>`
})
class StaticTreeComponent7 extends StaticTreeComponentBase {
}
@Component({
selector: 'tree',
inputs: ['data'],
directives: [StaticTreeComponent7],
template: `<span> {{data.value}} <tree [data]='data.right'></tree><tree [data]='data.left'></tree></span>`
})
class StaticTreeComponent8 extends StaticTreeComponentBase {
}
@Component({
selector: 'tree',
inputs: ['data'],
directives: [StaticTreeComponent8],
template: `<span> {{data.value}} <tree [data]='data.right'></tree><tree [data]='data.left'></tree></span>`
})
class StaticTreeComponent9 extends StaticTreeComponentBase {
}
@Component({
selector: 'app',
directives: [StaticTreeComponent9, NgIf],
template: `<tree *ngIf="initData != null" [data]='initData'></tree>`
})
class AppComponentWithStaticTree {
initData: TreeNode;
}