refactor(benchmark): simplify writing benchmarks

Adds `benchmarks/benchpress` module and adjusts the compiler
benchmarks to use it. Also adds the Angular 1.3 benchmark
to the compiler benchmarks.

Closes #202
This commit is contained in:
Tobias Bosch 2014-11-17 11:01:21 -08:00
parent be4cb2db3a
commit 8dfbc242af
13 changed files with 365 additions and 194 deletions

View File

@ -0,0 +1,63 @@
library benchmarks.benchpress;
import 'dart:js' as js;
import 'dart:html';
import 'dart:async';
// TODO: move the functionality of this module into benchpress and replace this
// file with a Dart wrapper!
var _benchmarkNames = [];
_benchmarkId(index) {
return "benchmark${index}";
}
_useBenchmark(index) {
var search = window.location.search;
if (search.length > 0) {
search = search.substring(1);
}
if (search.length > 0) {
return search == _benchmarkId(index);
} else {
return true;
}
}
_onLoad(callback) {
var isReady = document.readyState == 'complete';
if (isReady) {
Timer.run(callback);
} else {
window.addEventListener('load', (event) => callback(), false);
}
}
_createBenchmarkMenu() {
var div = document.createElement('div');
div.innerHtml += '<h1>Benchmarks:</h1><a class="btn btn-default" href="?">All</a>';
for (var i=0; i<_benchmarkNames.length; i++) {
var activeClass = _useBenchmark(i) ? 'active' : '';
div.innerHtml += '<a class="btn btn-default ${activeClass}" href="?${_benchmarkId(i)}">${_benchmarkNames[i]}</a>';
}
document.body.insertBefore(div, document.body.childNodes[0]);
}
benchmark(name, stepsCreationCallback) {
_benchmarkNames.add(name);
if (_benchmarkNames.length == 2) {
_onLoad(_createBenchmarkMenu);
}
if (_useBenchmark(_benchmarkNames.length-1)) {
stepsCreationCallback();
}
}
benchmarkStep(name, callback) {
var benchmarkName = _benchmarkNames[_benchmarkNames.length-1];
js.context['benchmarkSteps'].add(new js.JsObject.jsify({
"name": benchmarkName + '#' + name,
"fn": new js.JsFunction.withThis((_) => callback())
}));
}

View File

@ -0,0 +1,55 @@
// TODO: move the functionality of this module into benchpress itself!
var benchmarkNames = [];
function benchmarkId(index) {
return 'benchmark' + index;
}
function useBenchmark(index) {
var search = window.location.search;
if (search.length > 0) {
search = search.substring(1);
}
if (search.length > 0) {
return search == benchmarkId(index);
} else {
return true;
}
}
function onLoad(callback) {
var isReady = document.readyState === 'complete';
if (isReady) {
window.setTimeout(callback);
} else {
window.addEventListener('load', callback, false);
}
}
function createBenchmarkMenu() {
var div = document.createElement('div');
div.innerHTML += '<h1>Benchmarks:</h1><a class="btn btn-default" href="?">All</a>';
for (var i=0; i<benchmarkNames.length; i++) {
var activeClass = useBenchmark(i) ? 'active' : '';
div.innerHTML += ('<a class="btn btn-default '+activeClass+'" href="?'+benchmarkId(i)+'">'+benchmarkNames[i]+'</a>');
}
document.body.insertBefore(div, document.body.childNodes[0]);
}
export function benchmark(name, stepsCreationCallback) {
benchmarkNames.push(name);
if (benchmarkNames.length === 2) {
onLoad(createBenchmarkMenu);
}
if (useBenchmark(benchmarkNames.length-1)) {
stepsCreationCallback();
}
}
export function benchmarkStep(name, callback) {
var benchmarkName = benchmarkNames[benchmarkNames.length-1];
window.benchmarkSteps.push({
name: benchmarkName + '#' + name, fn: callback
});
}

View File

@ -2,35 +2,8 @@ library compiler_benchmark;
import './selector_benchmark.dart' as sbm;
import './compiler_benchmark.dart' as cbm;
import 'dart:js' as js;
main () {
sbm.setup();
cbm.setup();
js.context['benchmarkSteps'].add(new js.JsObject.jsify({
"name": "CssSelector.parse * 1000",
"fn": new js.JsFunction.withThis((_) => sbm.runParse())
}));
js.context['benchmarkSteps'].add(new js.JsObject.jsify({
"name": "SelectorMatcher.addSelectable * 1000",
"fn": new js.JsFunction.withThis((_) => sbm.runAdd())
}));
js.context['benchmarkSteps'].add(new js.JsObject.jsify({
"name": "SelectorMatcher.match * 1000",
"fn": new js.JsFunction.withThis((_) => sbm.runMatch())
}));
js.context['benchmarkSteps'].add(new js.JsObject.jsify({
"name": "Compiler.compile empty template",
"fn": new js.JsFunction.withThis((_) => cbm.compileEmptyTemplate())
}));
js.context['benchmarkSteps'].add(new js.JsObject.jsify({
"name": "Compiler.compile 25 element no bindings",
"fn": new js.JsFunction.withThis((_) => cbm.compile25ElementsNoBindings())
}));
js.context['benchmarkSteps'].add(new js.JsObject.jsify({
"name": "Compiler.compile 25 element with bindings",
"fn": new js.JsFunction.withThis((_) => cbm.compile25ElementsWithBindings())
}));
sbm.main();
cbm.main();
}

View File

@ -1,35 +1,9 @@
System.import('benchmarks/compiler/selector_benchmark').then(function (bm) {
bm.setup();
window.benchmarkSteps.push({name: 'CssSelector.parse * 1000', fn: bm.runParse});
}, console.log.bind(console));
System.import('benchmarks/compiler/selector_benchmark').then(function (bm) {
bm.setup();
window.benchmarkSteps.push({name: 'SelectorMatcher.addSelectable * 1000', fn: bm.runAdd});
}, console.log.bind(console));
System.import('benchmarks/compiler/selector_benchmark').then(function (bm) {
bm.setup();
window.benchmarkSteps.push({name: 'SelectorMatcher.match * 1000', fn: bm.runMatch});
}, console.log.bind(console));
System.import('benchmarks/compiler/compiler_benchmark').then(function (bm) {
bm.setup();
window.benchmarkSteps.push({name: 'Compiler.compile empty template', fn: bm.compileEmptyTemplate});
}, console.log.bind(console));
System.import('benchmarks/compiler/compiler_benchmark').then(function (bm) {
bm.setup();
window.benchmarkSteps.push({name: 'Compiler.compile 25 element no bindings', fn: bm.compile25ElementsNoBindings});
}, console.log.bind(console));
System.import('benchmarks/compiler/compiler_benchmark').then(function (bm) {
bm.setup();
window.benchmarkSteps.push({name: 'Compiler.compile 25 element with bindings', fn: bm.compile25ElementsWithBindings});
Promise.all([
System.import('benchmarks/compiler/selector_benchmark'),
System.import('benchmarks/compiler/compiler_benchmark'),
System.import('benchmarks/compiler/compiler_benchmark_ng13')
]).then(function (benchmarks) {
benchmarks.forEach(function(bm) {
bm.main();
});
}, console.log.bind(console));

View File

@ -4,7 +4,8 @@ module.exports = function(config) {
{src: '/js/traceur-runtime.js'},
{src: '/js/es6-module-loader-sans-promises.src.js'},
{src: '/js/extension-register.js'},
{src: 'register_system.js'},
{src: 'paths.js'},
{src: 'https://ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.js'},
{src: 'benchmark.js'}
]
});

View File

@ -1,4 +1,9 @@
import {DOM} from 'facade/dom';
import {benchmark, benchmarkStep} from '../benchpress';
import {DOM, document} from 'facade/dom';
import {isBlank} from 'facade/lang';
import {MapWrapper} from 'facade/collection';
import {AnnotatedType} from 'core/compiler/annotated_type';
import {Parser} from 'change_detection/parser/parser';
import {ClosureMap} from 'change_detection/parser/closure_map';
@ -11,39 +16,65 @@ import {Component} from 'core/annotations/component';
import {Decorator} from 'core/annotations/decorator';
import {TemplateConfig} from 'core/annotations/template_config';
var COUNT = 30;
var compiler;
var annotatedComponent;
var annotatedComponentNoDirectives;
var emptyTemplate;
var templateWith25ElementsNoBindings;
var templateWith25ElementsAndBindings;
export function setup() {
function setup() {
var closureMap = new ClosureMap();
var reflector = new Reflector();
var reflector = new CachingReflector();
compiler = new Compiler(null, reflector, new Parser(new Lexer(), closureMap), closureMap);
annotatedComponent = reflector.annotatedType(SomeComponent);
annotatedComponentNoDirectives = reflector.annotatedType(ComponentWithNoDirectives);
emptyTemplate = createTemplate('<div></div>');
templateWith25ElementsNoBindings = buildTemplateWith25ElementsNoBindings();
templateWith25ElementsAndBindings = buildTemplateWith25ElementsAndBindings();
annotatedComponent = reflector.annotatedType(BenchmarkComponent);
}
export function compileEmptyTemplate() {
var template = emptyTemplate;
return compiler.compileWithCache(null, annotatedComponent, template);
export function main() {
setup();
benchmark(`Compiler.compile 5*${COUNT} element no bindings`, function() {
var template = loadTemplate('templateNoBindings', COUNT);
benchmarkStep('run', function() {
// Need to clone every time as the compiler might modify the template!
var cloned = DOM.clone(template);
compiler.compileWithCache(null, annotatedComponent, cloned);
});
});
benchmark(`Compiler.compile 5*${COUNT} element with bindings`, function() {
var template = loadTemplate('templateWithBindings', COUNT);
benchmarkStep('run', function() {
// Need to clone every time as the compiler might modify the template!
var cloned = DOM.clone(template);
compiler.compileWithCache(null, annotatedComponent, cloned);
});
});
}
export function compile25ElementsWithBindings() {
var template = templateWith25ElementsAndBindings;
return compiler.compileWithCache(null, annotatedComponent, template);
function loadTemplate(templateId, repeatCount) {
var template = DOM.querySelectorAll(document, `#${templateId}`)[0];
var content = DOM.getInnerHTML(template);
var result = '';
for (var i=0; i<repeatCount; i++) {
result += content;
}
return DOM.createTemplate(result);
}
export function compile25ElementsNoBindings() {
var template = templateWith25ElementsNoBindings;
return compiler.compileWithCache(null, annotatedComponentNoDirectives, template);
// Caching reflector as reflection in Dart using Mirrors
class CachingReflector extends Reflector {
constructor() {
this._cache = MapWrapper.create();
}
annotatedType(type:Type):AnnotatedType {
var result = MapWrapper.get(this._cache, type);
if (isBlank(result)) {
result = super.annotatedType(type);
MapWrapper.set(this._cache, type, result);
}
return result;
}
}
@Decorator({
@ -91,79 +122,5 @@ class Dir4 {}
directives: [Dir0, Dir1, Dir2, Dir3, Dir4]
})
})
class SomeComponent {}
class BenchmarkComponent {}
@Component({
template: new TemplateConfig({
directives: []
})
})
class ComponentWithNoDirectives {}
// creates 25 nested divs without bindings, each looking like this:
// <div class="class0 class1 class2 class3 class4 " dir0="" attr0="value0" dir1="" attr1="value1" dir2="" attr2="value2" dir3="" attr3="value3" dir4="" attr4="value4">
// </div>
function buildTemplateWith25ElementsNoBindings() {
var result = '';
for (var i=0; i<5; i++) {
for (var j=0; j<5; j++) {
result += '<div class="';
for (var k=0; k<5; k++) {
result += `class${k} `;
}
result += '"';
for (var k=0; k<5; k++) {
result += ` dir${k}`;
result += ` attr${k}=value${k}`;
}
for (var k=0; k<5; k++) {
result += ` dir${k}`;
result += ` attr${k}=value${k}`;
}
result += '>';
}
for (var j=0; j<5; j++) {
result += '</div>';
}
}
return createTemplate(result);
}
// creates 25 nested divs , each looking like this:
// <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>
function buildTemplateWith25ElementsAndBindings() {
var result = '';
for (var i=0; i<5; i++) {
for (var j=0; j<5; j++) {
result += '<div class="';
for (var k=0; k<5; k++) {
result += `class${k} `;
}
result += '"';
for (var k=0; k<5; k++) {
result += ` dir${k}`;
result += ` [attr${k}]=value${k}`;
}
for (var k=0; k<5; k++) {
result += ` dir${k}`;
result += ` [attr${k}]=value${k}`;
}
result += '>';
for (var k=0; k<5; k++) {
result += `{{inter${k}}}`;
}
}
for (var j=0; j<5; j++) {
result += '</div>';
}
}
return createTemplate(result);
}
function createTemplate(html) {
return DOM.createTemplate(html);
}

View File

@ -0,0 +1,102 @@
import {benchmark, benchmarkStep} from '../benchpress';
var COUNT = 30;
var $compile;
export function main() {
benchmark(`Ng 1.3 Compiler.compile 5*${COUNT} element no bindings`, function() {
var template = loadTemplate('templateNoBindings', COUNT);
benchmarkStep('run', function() {
// Need to clone every time as the compiler might modify the template!
var cloned = template.cloneNode(true);
$compile(cloned);
});
});
benchmark(`Ng 1.3 Compiler.compile 5*${COUNT} element with bindings`, function() {
var template = loadTemplate('templateWithBindings', COUNT);
benchmarkStep('run', function() {
// Need to clone every time as the compiler might modify the template!
var cloned = template.cloneNode(true);
$compile(cloned);
});
});
var ngEl = document.createElement('div');
angular.bootstrap(ngEl, ['app']);
}
function loadTemplate(templateId, repeatCount) {
var template = document.querySelectorAll(`#${templateId}`)[0];
var content = template.innerHTML;
var result = '';
for (var i=0; i<repeatCount; i++) {
result += content;
}
// replace [] binding syntax
result = result.replace(/[\[\]]/g, '');
// Use a DIV as container as Angular 1.3 does not know <template> elements...
var div = document.createElement('div');
div.innerHTML = result;
return div;
}
angular.module('app', [])
.directive('dir0', function($parse) {
return {
compile: function($element, $attrs) {
var expr = $parse($attrs.attr0);
return function($scope) {
$scope.$watch(expr, angular.noop);
}
}
};
})
.directive('dir1', function($parse) {
return {
compile: function($element, $attrs) {
var expr = $parse($attrs.attr1);
return function($scope) {
$scope.$watch(expr, angular.noop);
}
}
};
})
.directive('dir2', function($parse) {
return {
compile: function($element, $attrs) {
var expr = $parse($attrs.attr2);
return function($scope) {
$scope.$watch(expr, angular.noop);
}
}
};
})
.directive('dir3', function($parse) {
return {
compile: function($element, $attrs) {
var expr = $parse($attrs.attr3);
return function($scope) {
$scope.$watch(expr, angular.noop);
}
}
};
})
.directive('dir4', function($parse) {
return {
compile: function($element, $attrs) {
var expr = $parse($attrs.attr4);
return function($scope) {
$scope.$watch(expr, angular.noop);
}
}
};
})
.run(function(_$compile_) {
$compile = _$compile_;
});

View File

@ -0,0 +1,30 @@
<template id="templateNoBindings">
<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>
</template>
<template id="templateWithBindings">
<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>
</template>

View File

@ -1,3 +1,5 @@
import {benchmark, benchmarkStep} from '../benchpress';
import {SelectorMatcher} from "core/compiler/selector";
import {CssSelector} from "core/compiler/selector";
import {StringWrapper, Math} from 'facade/lang';
@ -9,46 +11,55 @@ var fixedSelectors = [];
var COUNT = 1000;
export function setup() {
for (var i=0; i<COUNT; i++) {
export function main() {
setup(COUNT);
benchmark(`cssSelector.parse * ${COUNT}`, function() {
benchmarkStep(`run`, function() {
var result = [];
for (var i=0; i<COUNT; i++) {
ListWrapper.push(result, CssSelector.parse(fixedSelectorStrings[i]));
}
return result;
});
});
benchmark(`cssSelector.addSelectable * ${COUNT}`, function() {
benchmarkStep(`run`, function() {
var matcher = new SelectorMatcher();
for (var i=0; i<COUNT; i++) {
matcher.addSelectable(fixedSelectors[i], i);
}
return matcher;
});
});
benchmark(`cssSelector.match * ${COUNT}`, function() {
benchmarkStep(`run`, function() {
var matchCount = 0;
for (var i=0; i<COUNT; i++) {
fixedMatcher.match(fixedSelectors[i], (selected) => {
matchCount += selected;
});
}
return matchCount;
});
});
}
function setup(count) {
for (var i=0; i<count; i++) {
ListWrapper.push(fixedSelectorStrings, randomSelector());
}
for (var i=0; i<COUNT; i++) {
for (var i=0; i<count; i++) {
ListWrapper.push(fixedSelectors, CssSelector.parse(fixedSelectorStrings[i]));
}
fixedMatcher = new SelectorMatcher();
for (var i=0; i<COUNT; i++) {
for (var i=0; i<count; i++) {
fixedMatcher.addSelectable(fixedSelectors[i], i);
}
}
export function runParse() {
var result = [];
for (var i=0; i<COUNT; i++) {
ListWrapper.push(result, CssSelector.parse(fixedSelectorStrings[i]));
}
return result;
}
export function runAdd() {
var matcher = new SelectorMatcher();
for (var i=0; i<COUNT; i++) {
matcher.addSelectable(fixedSelectors[i], i);
}
return matcher;
}
export function runMatch() {
// The sum is used to prevent Chrome from optimizing the loop away...
var count = 0;
for (var i=0; i<COUNT; i++) {
fixedMatcher.match(fixedSelectors[i], (selected) => {
count += selected;
});
}
return count;
}
function randomSelector() {
var res = randomStr(5);
for (var i=0; i<3; i++) {

View File

@ -1,3 +1,5 @@
library change_detection.parser.closure_map;
import 'dart:mirrors';
typedef SetterFn(Object obj, value);

View File

@ -3,7 +3,7 @@ library angular.core.facade.dom;
import 'dart:html';
import 'dart:js' show JsObject;
export 'dart:html' show DocumentFragment, Node, Element, TemplateElement, Text;
export 'dart:html' show DocumentFragment, Node, Element, TemplateElement, Text, document, location;
// TODO(tbosch): Is there a builtin one? Why is Dart
// removing unknown elements by default?

View File

@ -4,6 +4,9 @@ export var NodeList = window.NodeList;
export var Text = window.Text;
export var Element = window.HTMLElement;
export var TemplateElement = window.HTMLTemplateElement;
export var document = window.document;
export var location = window.location;
import {List, MapWrapper} from 'facade/collection';
export class DOM {