mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-04-01 08:56:08 +08:00
refactor(perf): migration event delegation benchmark to benchpress
This commit is contained in:
57
benchmarks/event-delegation-bp/app.js
Normal file
57
benchmarks/event-delegation-bp/app.js
Normal file
@@ -0,0 +1,57 @@
|
||||
var app = angular.module('eventDelegationBenchmark', []);
|
||||
|
||||
app.directive('noopDir', function() {
|
||||
return {
|
||||
compile: function($element, $attrs) {
|
||||
return function($scope, $element) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.directive('nativeClick', ['$parse', function($parse) {
|
||||
return {
|
||||
compile: function($element, $attrs) {
|
||||
var expr = $parse($attrs.tstEvent);
|
||||
return function($scope, $element) {
|
||||
$element[0].addEventListener('click', function() {
|
||||
console.log('clicked');
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
app.directive('dlgtClick', function() {
|
||||
return {
|
||||
compile: function($element, $attrs) {
|
||||
var evt = $attrs.dlgtClick;
|
||||
// We don't setup the global event listeners as the costs are small and one time only...
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
app.controller('DataController', function($rootScope) {
|
||||
this.ngRepeatCount = 1000;
|
||||
this.rows = [];
|
||||
var self = this;
|
||||
|
||||
benchmarkSteps.push({
|
||||
name: '$apply',
|
||||
fn: function() {
|
||||
var oldRows = self.rows;
|
||||
$rootScope.$apply(function() {
|
||||
self.rows = [];
|
||||
});
|
||||
self.rows = oldRows;
|
||||
if (self.rows.length !== self.ngRepeatCount) {
|
||||
self.rows = [];
|
||||
for (var i=0; i<self.ngRepeatCount; i++) {
|
||||
self.rows.push('row'+i);
|
||||
}
|
||||
}
|
||||
$rootScope.$apply();
|
||||
}
|
||||
});
|
||||
});
|
||||
10
benchmarks/event-delegation-bp/bp.conf.js
Normal file
10
benchmarks/event-delegation-bp/bp.conf.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
scripts: [{
|
||||
id: 'angular',
|
||||
src: '/build/angular.js'
|
||||
},{
|
||||
src: 'app.js',
|
||||
}]
|
||||
});
|
||||
};
|
||||
139
benchmarks/event-delegation-bp/main.html
Normal file
139
benchmarks/event-delegation-bp/main.html
Normal file
@@ -0,0 +1,139 @@
|
||||
<div ng-app="eventDelegationBenchmark">
|
||||
<div ng-controller="DataController as ctrl">
|
||||
<div class="container-fluid">
|
||||
|
||||
<p>
|
||||
Impact of event delegation.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label>
|
||||
Number of ngRepeats:
|
||||
<input type="number" ng-model="ctrl.ngRepeatCount">
|
||||
</label>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<div class="radio"><label><input type=radio ng-model="benchmarkType" value="ngClick">ngClick</label></div>
|
||||
<div class="radio"><label><input type=radio ng-model="benchmarkType" value="ngClickNoJqLite">ngClick without jqLite</label></div>
|
||||
<div class="radio"><label><input type=radio ng-model="benchmarkType" value="ngShow">baseline: ng-show</label></div>
|
||||
<div class="radio"><label><input type=radio ng-model="benchmarkType" value="textInterpolation">baseline: text interpolation</label></div>
|
||||
<div class="radio"><label><input type=radio ng-model="benchmarkType" value="dlgtClick">delegate event directive (only compile)</label></div>
|
||||
<div class="radio"><label><input type=radio ng-model="benchmarkType" value="noopDir">baseline: noop directive (compile and link)</label></div>
|
||||
<div class="radio"><label><input type=radio ng-model="benchmarkType" value="noop">baseline: no directive</label></div>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
How to read the results:
|
||||
<ul>
|
||||
<li>The benchmark measures how long it takes to instantiate a given number of directives</li>
|
||||
<li>ngClick is compared against ngShow and text interpolation as baseline. The results show
|
||||
how expensive ngClick is compared to other very simple directives that touch the DOM.
|
||||
</li>
|
||||
<li>To measure the impact of jqLite.on vs element.addEventListener there is also a benchmark
|
||||
that as a modified version of ngClick that uses element.addEventListener.
|
||||
</li>
|
||||
<li>The delegate event directive is compared against a noop directive with a compile and link function and the case with no directives.
|
||||
The result shows how expensive it is to add a link function to a directive, as the delegate event directive has none.
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Results as of 7/31/2014:
|
||||
<ul>
|
||||
<li>ngClick is very close to ngShow and text interpolation, especially when looking at a version of ngClick that does not use jqLite.on but element.addEventListener instead.</li>
|
||||
<li>A delegate event directive that has no link function has the same speed as a directive with link function. I.e. ngClick is slower compared to the delegate event directive only because ngClick touches
|
||||
the DOM for every element</li>
|
||||
<li>A delegate event directive could be about 50% faster than ngClick. However, the overall performance
|
||||
benefit depends on how many (and which) other directives are used on the same element
|
||||
and what other things are part of the measures use case.
|
||||
E.g. rows of a table with ngRepeat that use ngClick will probably also contain text interpolation.
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
Debug output:
|
||||
<ng-switch on="benchmarkType">
|
||||
<div ng-switch-when="ngClick">
|
||||
<div>
|
||||
<span ng-repeat="row in ctrl.rows">
|
||||
<span ng-click="a()">1</span>
|
||||
<span ng-click="a()">1</span>
|
||||
<span ng-click="a()">1</span>
|
||||
<span ng-click="a()">1</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="ngClickNoJqLite">
|
||||
<div>
|
||||
<span ng-repeat="row in ctrl.rows">
|
||||
<span native-click="a()">1</span>
|
||||
<span native-click="a()">1</span>
|
||||
<span native-click="a()">1</span>
|
||||
<span native-click="a()">1</span>
|
||||
<span native-click="a()">1</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="ngShow">
|
||||
<div>
|
||||
<span ng-repeat="row in ctrl.rows">
|
||||
<span ng-show="true">1</span>
|
||||
<span ng-show="true">1</span>
|
||||
<span ng-show="true">1</span>
|
||||
<span ng-show="true">1</span>
|
||||
<span ng-show="true">1</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="textInterpolation">
|
||||
<div>
|
||||
<span ng-repeat="row in ctrl.rows">
|
||||
<span>{{row}}</span>
|
||||
<span>{{row}}</span>
|
||||
<span>{{row}}</span>
|
||||
<span>{{row}}</span>
|
||||
<span>{{row}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="dlgtClick">
|
||||
<div>
|
||||
<span ng-repeat="row in ctrl.rows">
|
||||
<span dlgt-click="a()">1</span>
|
||||
<span dlgt-click="a()">1</span>
|
||||
<span dlgt-click="a()">1</span>
|
||||
<span dlgt-click="a()">1</span>
|
||||
<span dlgt-click="a()">1</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="noopDir">
|
||||
<div>
|
||||
<span ng-repeat="row in ctrl.rows">
|
||||
<span noop-dir>1</span>
|
||||
<span noop-dir>1</span>
|
||||
<span noop-dir>1</span>
|
||||
<span noop-dir>1</span>
|
||||
<span noop-dir>1</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-switch-when="noop">
|
||||
<div>
|
||||
<span ng-repeat="row in ctrl.rows">
|
||||
<span>1</span>
|
||||
<span>1</span>
|
||||
<span>1</span>
|
||||
<span>1</span>
|
||||
<span>1</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ng-switch>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,7 +54,6 @@
|
||||
"node-html-encoder": "0.0.2",
|
||||
"sorted-object": "^1.0.0",
|
||||
"qq": "^0.3.5",
|
||||
"benchmark": "1.x.x",
|
||||
"angular-benchpress": "0.x.x"
|
||||
},
|
||||
"licenses": [
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
var app = angular.module('perf', ['ngBench'])
|
||||
.directive('noopDir', function() {
|
||||
return {
|
||||
compile: function($element, $attrs) {
|
||||
return function($scope, $element) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
app.directive('nativeClick', ['$parse', function($parse) {
|
||||
return {
|
||||
compile: function($element, $attrs) {
|
||||
var expr = $parse($attrs.tstEvent);
|
||||
return function($scope, $element) {
|
||||
$element[0].addEventListener('click', function() {
|
||||
console.log('clicked');
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
}])
|
||||
.directive('dlgtClick', function() {
|
||||
return {
|
||||
compile: function($element, $attrs) {
|
||||
var evt = $attrs.dlgtClick;
|
||||
// We don't setup the global event listeners as the costs are small and one time only...
|
||||
}
|
||||
};
|
||||
})
|
||||
.controller('MainCtrl', ['$compile', '$rootScope', '$templateCache',
|
||||
function($compile, $rootScope, $templateCache) {
|
||||
// TODO: Make ngRepeatCount configurable via the UI!
|
||||
var self = this;
|
||||
this.ngRepeatCount = 20;
|
||||
this.manualRepeatCount = 5;
|
||||
this.benchmarks = [{
|
||||
title: 'ng-click',
|
||||
factory: function() {
|
||||
return createBenchmark({
|
||||
directive: 'ng-click="a()"'
|
||||
});
|
||||
},
|
||||
active: true
|
||||
},{
|
||||
title: 'ng-click without jqLite',
|
||||
factory: function() {
|
||||
return createBenchmark({
|
||||
directive: 'native-click="a()"'
|
||||
});
|
||||
},
|
||||
active: true
|
||||
},{
|
||||
title: 'baseline: ng-show',
|
||||
factory: function() {
|
||||
return createBenchmark({
|
||||
directive: 'ng-show="true"'
|
||||
});
|
||||
},
|
||||
active: true
|
||||
},{
|
||||
title: 'baseline: text interpolation',
|
||||
factory: function() {
|
||||
return createBenchmark({
|
||||
text: '{{row}}'
|
||||
});
|
||||
},
|
||||
active: true
|
||||
},{
|
||||
title: 'delegate event directive (only compile)',
|
||||
factory: function() {
|
||||
return createBenchmark({
|
||||
directive: 'dlgt-click="a()"'
|
||||
});
|
||||
},
|
||||
active: true
|
||||
},{
|
||||
title: 'baseline: noop directive (compile and link)',
|
||||
factory: function() {
|
||||
return createBenchmark({
|
||||
directive: 'noop-dir'
|
||||
});
|
||||
},
|
||||
active: true
|
||||
},{
|
||||
title: 'baseline: no directive',
|
||||
factory: function() {
|
||||
return createBenchmark({});
|
||||
},
|
||||
active: true
|
||||
}];
|
||||
|
||||
function createBenchmark(options) {
|
||||
options.directive = options.directive || '';
|
||||
options.text = options.text || '';
|
||||
|
||||
var templateHtml = '<div><span ng-repeat="row in rows">';
|
||||
for (var i=0; i<self.manualRepeatCount; i++) {
|
||||
templateHtml += '<span '+options.directive+'>'+options.text+'</span>';
|
||||
}
|
||||
templateHtml += '</span></div>';
|
||||
|
||||
var compiledTemplate = $compile(templateHtml);
|
||||
var rows = [];
|
||||
for (var i=0; i<self.ngRepeatCount; i++) {
|
||||
rows.push('row'+i);
|
||||
}
|
||||
return function(container) {
|
||||
var scope = $rootScope.$new();
|
||||
try {
|
||||
scope.rows = rows;
|
||||
compiledTemplate(scope, function(clone) {
|
||||
container.appendChild(clone[0]);
|
||||
});
|
||||
scope.$digest();
|
||||
} finally {
|
||||
scope.$destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
}])
|
||||
@@ -1,69 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html ng-app="perf" ng-controller="MainCtrl as ctrl">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Event delegation</title>
|
||||
|
||||
<script src="../../../build/angular.js"></script>
|
||||
<script src="../../../node_modules/benchmark/benchmark.js"></script>
|
||||
<script src="ng_benchmark.js"></script>
|
||||
<script src="app.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>
|
||||
Benchmark: impact of event delegation
|
||||
</h1>
|
||||
|
||||
How to run:
|
||||
<ul>
|
||||
<li>For most stable results, run this in Chrome with the following command line option:
|
||||
<pre>--js-flags="--expose-gc"</pre>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
How to read the results:
|
||||
<ul>
|
||||
<li>The benchmark measures how long it takes to instantiate a given number of directives</li>
|
||||
<li>ngClick is compared against ngShow and text interpolation as baseline. The results show
|
||||
how expensive ngClick is compared to other very simple directives that touch the DOM.
|
||||
</li>
|
||||
<li>To measure the impact of jqLite.on vs element.addEventListener there is also a benchmark
|
||||
that as a modified version of ngClick that uses element.addEventListener.
|
||||
</li>
|
||||
<li>The delegate event directive is compared against a noop directive with a compile and link function and the case with no directives.
|
||||
The result shows how expensive it is to add a link function to a directive, as the delegate event directive has none.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
Results as of 7/31/2014:
|
||||
<ul>
|
||||
<li>ngClick is very close to ngShow and text interpolation, especially when looking at a version of ngClick that does not use jqLite.on but element.addEventListener instead.</li>
|
||||
<li>A delegate event directive that has no link function has the same speed as a directive with link function. I.e. ngClick is slower compared to the delegate event directive only because ngClick touches
|
||||
the DOM for every element</li>
|
||||
<li>A delegate event directive could be about 2x faster than ngClick. However, the overall performance
|
||||
benefit depends on how many (and which) other directives are used on the same element
|
||||
and what other things are part of the measures use case.
|
||||
E.g. rows of a table with ngRepeat that use ngClick will probably also contain text interpolation.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
Benchmark Options:
|
||||
<p>
|
||||
<label>
|
||||
Number of ngRepeats:
|
||||
<input type="number" ng-model="ctrl.ngRepeatCount">
|
||||
</label>
|
||||
<br>
|
||||
<label>
|
||||
Number of manual repeats inside the ngRepeat:
|
||||
<input type="number" ng-model="ctrl.manualRepeatCount">
|
||||
</label>
|
||||
</p>
|
||||
|
||||
<div ng-bench="ctrl.benchmarks"></div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,27 +0,0 @@
|
||||
Benchmarks:
|
||||
<table>
|
||||
<thead>
|
||||
<td>Name</td>
|
||||
<td>State</td>
|
||||
<td>Result</td>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="bench in benchmarks">
|
||||
<td>
|
||||
<label><input type="checkbox" ng-model="bench.active">{{bench.title}}
|
||||
</label>
|
||||
</td>
|
||||
<td>{{bench.state}}</td>
|
||||
<td>{{bench.lastResult}}</td>
|
||||
</tr>
|
||||
<tbody>
|
||||
</table>
|
||||
|
||||
<div>
|
||||
<button ng-click="ngBenchCtrl.toggleAll()">Toggle all</button>
|
||||
<button ng-click="ngBenchCtrl.run()">Run</button>
|
||||
<button ng-click="ngBenchCtrl.runOnce()">Debug once</button>
|
||||
</div>
|
||||
|
||||
Benchmark work area:
|
||||
<div class="work" style="height:20px; overflow: auto"></div>
|
||||
@@ -1,104 +0,0 @@
|
||||
(function() {
|
||||
|
||||
var ngBenchmarkTemplateUrl = getCurrentScript().replace('.js', '.html');
|
||||
|
||||
angular.module('ngBench', []).directive('ngBench', function() {
|
||||
return {
|
||||
scope: {
|
||||
'benchmarks': '=ngBench'
|
||||
},
|
||||
templateUrl: ngBenchmarkTemplateUrl,
|
||||
controllerAs: 'ngBenchCtrl',
|
||||
controller: ['$scope', '$element', NgBenchController]
|
||||
};
|
||||
});
|
||||
|
||||
function NgBenchController($scope, $element) {
|
||||
var container = $element[0].querySelector('.work');
|
||||
|
||||
this.toggleAll = function() {
|
||||
var newState = !$scope.benchmarks[0].active;
|
||||
$scope.benchmarks.forEach(function(benchmark) {
|
||||
benchmark.active = newState;
|
||||
});
|
||||
};
|
||||
|
||||
this.run = function() {
|
||||
var suite = new Benchmark.Suite();
|
||||
$scope.benchmarks.forEach(function(benchmark) {
|
||||
var options = {
|
||||
'model': benchmark,
|
||||
'onStart': function() {
|
||||
benchmark.state = 'running';
|
||||
$scope.$digest();
|
||||
},
|
||||
'setup': function() {
|
||||
window.gc && window.gc();
|
||||
},
|
||||
'onComplete': function(event) {
|
||||
benchmark.state = '';
|
||||
if (this.error) {
|
||||
benchmark.lastResult = this.error.stack;
|
||||
} else {
|
||||
benchmark.lastResult = benchResultToString(this);
|
||||
}
|
||||
$scope.$digest();
|
||||
},
|
||||
delegate: createBenchmarkFn(benchmark.factory)
|
||||
};
|
||||
benchmark.state = '';
|
||||
if (benchmark.active) {
|
||||
benchmark.state = 'waiting';
|
||||
suite.add(benchmark.title, 'this.delegate()', options);
|
||||
}
|
||||
});
|
||||
suite.run({'async': true});
|
||||
};
|
||||
|
||||
this.runOnce = function() {
|
||||
window.setTimeout(function() {
|
||||
$scope.benchmarks.forEach(function(benchmark) {
|
||||
benchmark.state = '';
|
||||
if (benchmark.active) {
|
||||
try {
|
||||
createBenchmarkFn(benchmark.factory)();
|
||||
benchmark.lastResult = '';
|
||||
} catch (e) {
|
||||
benchmark.lastResult = e.message;
|
||||
}
|
||||
}
|
||||
});
|
||||
$scope.$digest();
|
||||
});
|
||||
};
|
||||
|
||||
function createBenchmarkFn(factory) {
|
||||
var instance = factory();
|
||||
return function() {
|
||||
container.innerHTML = '';
|
||||
instance(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// See benchmark.js, toStringBench,
|
||||
// but without showing the name
|
||||
function benchResultToString(bench) {
|
||||
var me = bench,
|
||||
hz = me.hz,
|
||||
stats = me.stats,
|
||||
size = stats.sample.length;
|
||||
|
||||
return Benchmark.formatNumber(hz.toFixed(hz < 100 ? 2 : 0)) + ' ops/sec +/-' +
|
||||
stats.rme.toFixed(2) + '% (' + size + ' run' + (size == 1 ? '' : 's') + ' sampled)';
|
||||
}
|
||||
|
||||
function getCurrentScript() {
|
||||
var script = document.currentScript;
|
||||
if (!script) {
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
script = scripts[scripts.length - 1];
|
||||
}
|
||||
return script.src;
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user