diff --git a/package.json b/package.json index e5ee518a..6c8cc185 100644 --- a/package.json +++ b/package.json @@ -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": [ { diff --git a/perf/apps/event-delegation/app.js b/perf/apps/event-delegation/app.js new file mode 100755 index 00000000..aeb48684 --- /dev/null +++ b/perf/apps/event-delegation/app.js @@ -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 = '
'; + for (var i=0; i'+options.text+''; + } + templateHtml += '
'; + + var compiledTemplate = $compile(templateHtml); + var rows = []; + for (var i=0; i + + + + Event delegation + + + + + + + + + +

+Benchmark: impact of event delegation +

+ +How to run: + + +How to read the results: + + +Results as of 7/31/2014: + + +Benchmark Options: +

+ +
+ +

+ +
+ + + + diff --git a/perf/apps/event-delegation/ng_benchmark.html b/perf/apps/event-delegation/ng_benchmark.html new file mode 100644 index 00000000..25469be2 --- /dev/null +++ b/perf/apps/event-delegation/ng_benchmark.html @@ -0,0 +1,27 @@ +Benchmarks: + + + + + + + + + + + + + +
NameStateResult
+ + {{bench.state}}{{bench.lastResult}}
+ +
+ + + +
+ +Benchmark work area: +
\ No newline at end of file diff --git a/perf/apps/event-delegation/ng_benchmark.js b/perf/apps/event-delegation/ng_benchmark.js new file mode 100644 index 00000000..72e6c6b4 --- /dev/null +++ b/perf/apps/event-delegation/ng_benchmark.js @@ -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; + } +})();