From a353ed834ce529c3b4ccb8d1ea4e5b32c6415ff4 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Fri, 15 Aug 2014 11:25:05 -0700 Subject: [PATCH] refactor(perf): migration event delegation benchmark to benchpress --- benchmarks/event-delegation-bp/app.js | 57 ++++++++ benchmarks/event-delegation-bp/bp.conf.js | 10 ++ benchmarks/event-delegation-bp/main.html | 139 +++++++++++++++++++ package.json | 1 - perf/apps/event-delegation/app.js | 121 ---------------- perf/apps/event-delegation/index.html | 69 --------- perf/apps/event-delegation/ng_benchmark.html | 27 ---- perf/apps/event-delegation/ng_benchmark.js | 104 -------------- 8 files changed, 206 insertions(+), 322 deletions(-) create mode 100644 benchmarks/event-delegation-bp/app.js create mode 100644 benchmarks/event-delegation-bp/bp.conf.js create mode 100644 benchmarks/event-delegation-bp/main.html delete mode 100755 perf/apps/event-delegation/app.js delete mode 100755 perf/apps/event-delegation/index.html delete mode 100644 perf/apps/event-delegation/ng_benchmark.html delete mode 100644 perf/apps/event-delegation/ng_benchmark.js diff --git a/benchmarks/event-delegation-bp/app.js b/benchmarks/event-delegation-bp/app.js new file mode 100644 index 00000000..237193ad --- /dev/null +++ b/benchmarks/event-delegation-bp/app.js @@ -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 +
+
+ +

+Impact of event delegation. +

+ +

+ +

+ +

+

+
+
+
+
+
+
+

+ +

+How to read the results: +

    +
  • The benchmark measures how long it takes to instantiate a given number of directives
  • +
  • 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. +
  • +
  • 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. +
  • +
  • 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. +
  • +
+

+ +

+Results as of 7/31/2014: +

    +
  • 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.
  • +
  • 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
  • +
  • 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. +
  • +
+

+ +Debug output: + +
+
+ + 1 + 1 + 1 + 1 + +
+
+
+
+ + 1 + 1 + 1 + 1 + 1 + +
+
+
+
+ + 1 + 1 + 1 + 1 + 1 + +
+
+
+
+ + {{row}} + {{row}} + {{row}} + {{row}} + {{row}} + +
+
+
+
+ + 1 + 1 + 1 + 1 + 1 + +
+
+
+
+ + 1 + 1 + 1 + 1 + 1 + +
+
+
+
+ + 1 + 1 + 1 + 1 + 1 + +
+
+ +
+ +
+
+ \ No newline at end of file diff --git a/package.json b/package.json index 3d0e4431..cb60c723 100644 --- a/package.json +++ b/package.json @@ -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": [ diff --git a/perf/apps/event-delegation/app.js b/perf/apps/event-delegation/app.js deleted file mode 100755 index aeb48684..00000000 --- a/perf/apps/event-delegation/app.js +++ /dev/null @@ -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 = '
'; - 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: -
    -
  • For most stable results, run this in Chrome with the following command line option: -
    --js-flags="--expose-gc"
    -
  • -
- -How to read the results: -
    -
  • The benchmark measures how long it takes to instantiate a given number of directives
  • -
  • 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. -
  • -
  • 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. -
  • -
  • 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. -
  • -
- -Results as of 7/31/2014: -
    -
  • 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.
  • -
  • 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
  • -
  • 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. -
  • -
- -Benchmark Options: -

- -
- -

- -
- - - - diff --git a/perf/apps/event-delegation/ng_benchmark.html b/perf/apps/event-delegation/ng_benchmark.html deleted file mode 100644 index 25469be2..00000000 --- a/perf/apps/event-delegation/ng_benchmark.html +++ /dev/null @@ -1,27 +0,0 @@ -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 deleted file mode 100644 index 72e6c6b4..00000000 --- a/perf/apps/event-delegation/ng_benchmark.js +++ /dev/null @@ -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; - } -})();