mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-01-12 22:45:52 +08:00
chore(perf): add event delegation benchmark
This commit is contained in:
@@ -53,7 +53,8 @@
|
||||
"jshint-stylish": "~0.1.5",
|
||||
"node-html-encoder": "0.0.2",
|
||||
"sorted-object": "^1.0.0",
|
||||
"qq": "^0.3.5"
|
||||
"qq": "^0.3.5",
|
||||
"benchmark": "1.x.x"
|
||||
},
|
||||
"licenses": [
|
||||
{
|
||||
|
||||
121
perf/apps/event-delegation/app.js
Executable file
121
perf/apps/event-delegation/app.js
Executable file
@@ -0,0 +1,121 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}])
|
||||
69
perf/apps/event-delegation/index.html
Executable file
69
perf/apps/event-delegation/index.html
Executable file
@@ -0,0 +1,69 @@
|
||||
<!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>
|
||||
27
perf/apps/event-delegation/ng_benchmark.html
Normal file
27
perf/apps/event-delegation/ng_benchmark.html
Normal file
@@ -0,0 +1,27 @@
|
||||
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>
|
||||
104
perf/apps/event-delegation/ng_benchmark.js
Normal file
104
perf/apps/event-delegation/ng_benchmark.js
Normal file
@@ -0,0 +1,104 @@
|
||||
(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