├── .gitignore ├── results.png ├── .travis.yml ├── .jscsrc ├── lib ├── run.js └── jsperf.js ├── package.json ├── LICENSE ├── test ├── test.js ├── test-page.html └── test-page2.html ├── README.md ├── bin └── jsperf.js └── .jshintrc /.gitignore: -------------------------------------------------------------------------------- 1 | jsperf.com 2 | node_modules -------------------------------------------------------------------------------- /results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OrKoN/jsperf/HEAD/results.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | - "5" 5 | - "6" 6 | 7 | script: 8 | - npm test -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "google", 3 | "esnext": true, 4 | "disallowTrailingComma": false, 5 | "requireCommaBeforeLineBreak": false, 6 | "requireSpaceBetweenArguments": false, 7 | "requireCurlyBraces": false, 8 | "requireSpaceBetweenArguments": true, 9 | "disallowSpacesInsideArrayBrackets": false, 10 | "disallowSpacesInsideObjectBrackets": false 11 | } 12 | 13 | -------------------------------------------------------------------------------- /lib/run.js: -------------------------------------------------------------------------------- 1 | var spawn = require('cross-spawn-async'); 2 | 3 | function run(cmd, pipe) { 4 | return new Promise(function(resolve, reject) { 5 | var cmdArr = cmd.split(' '); 6 | var data = []; 7 | var child = spawn(cmdArr[0], cmdArr.slice(1), 8 | {stdio: pipe ? 'pipe' : 'inherit'}); 9 | 10 | if (child.stdout) { 11 | child.stdout.on('data', function(chunk) { 12 | data.push(chunk.toString('utf-8')); 13 | }); 14 | } 15 | 16 | child.on('close', function(error) { 17 | if (!error) { 18 | resolve(data.join()); 19 | } else { 20 | reject(error, data.join()); 21 | } 22 | }); 23 | }); 24 | } 25 | 26 | module.exports = run; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsperf", 3 | "version": "0.2.2", 4 | "description": "Run jsperf.com tests locally with your NodeJS version", 5 | "main": "lib/jsperf.js", 6 | "scripts": { 7 | "test": "./node_modules/mocha/bin/mocha" 8 | }, 9 | "keywords": [ 10 | "jsperf", 11 | "performance", 12 | "nodejs" 13 | ], 14 | "author": "Alex Rudenko ", 15 | "license": "MIT", 16 | "bin": { 17 | "jsperf": "./bin/jsperf.js" 18 | }, 19 | "dependencies": { 20 | "cheerio": "^0.20.0", 21 | "cross-spawn-async": "^2.1.9", 22 | "js-beautify": "^1.6.2", 23 | "js-string-escape": "^1.0.1", 24 | "minimist": "^1.2.0", 25 | "mkdirp": "^0.5.1", 26 | "request": "^2.69.0" 27 | }, 28 | "devDependencies": { 29 | "mocha": "^2.4.5", 30 | "nock": "^7.2.2", 31 | "beautify-benchmark": "^0.2.4", 32 | "benchmark": "^2.1.0" 33 | }, 34 | "engine": "node >= 0.4.0" 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Oleksii Rudenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /* globals it, describe */ 2 | 3 | var JsPerf = require('../lib/jsperf'); 4 | var assert = require('assert'); 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | var nock = require('nock'); 8 | 9 | describe('JS PERF', function() { 10 | it('should work', function(done) { 11 | this.timeout(0); 12 | var testCase = 'replace-vs-split-join-vs-replaceall'; 13 | var rev = '67'; 14 | var inst = new JsPerf(testCase, rev); 15 | var scope = nock('https://jsperf.com') 16 | .get(`/${testCase}/${rev}`) 17 | .reply(200, fs.readFileSync('./test/test-page.html', 'utf-8')); 18 | inst 19 | .get() 20 | .then(() => { 21 | inst 22 | .run() 23 | .then(done) 24 | .catch(done); 25 | }); 26 | }); 27 | 28 | it('should find missing setup pieces', function(done) { 29 | this.timeout(0); 30 | var testCase = 'regexp-test-vs-match-m5'; 31 | var rev = '24'; 32 | var inst = new JsPerf(testCase, rev); 33 | var scope = nock('https://jsperf.com') 34 | .get(`/${testCase}/${rev}`) 35 | .reply(200, fs.readFileSync('./test/test-page2.html', 'utf-8')); 36 | inst 37 | .get() 38 | .then(() => { 39 | inst 40 | .run() 41 | .then(done) 42 | .catch(done); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSPerf for NodeJS 2 | 3 | [![Version](https://img.shields.io/npm/v/jsperf.svg)](https://www.npmjs.com/package/jsperf) 4 | [![Build Status](http://img.shields.io/travis/OrKoN/jsperf.svg?style=flat)](https://travis-ci.org/OrKoN/jsperf) 5 | [![Downloads](https://img.shields.io/npm/dm/jsperf.svg)](https://www.npmjs.com/package/jsperf) 6 | [![Dependencies](https://img.shields.io/david/OrKoN/jsperf.svg)](https://github.com/OrKoN/jsperf/blob/master/package.json#L19) 7 | 8 | This command line utility helps you run performance tests from http://jsperf.com locally with NodeJS. 9 | 10 | ## Installation 11 | 12 | ```sh 13 | npm install jsperf -g 14 | ``` 15 | 16 | ## Warning 17 | 18 | **The tool runs the code fetched from jsperf.com w/o any processing. This can be very dangerous. Review the tests before running or use disposable sandboxes** 19 | 20 | This also means that browser-dependant tests will not run (at least with the current version). Fetch only pure JS tests. 21 | 22 | ## Usage 23 | 24 | Create a folder for your local tests: 25 | 26 | ```sh 27 | mkdir my-jsperf-tests 28 | ``` 29 | 30 | Initialize your tests: 31 | 32 | ```sh 33 | cd my-jsperf-tests 34 | jsperf init 35 | ``` 36 | 37 | Get a test: 38 | 39 | ```sh 40 | jsperf get 41 | ``` 42 | 43 | For instance: 44 | 45 | ```sh 46 | jsperf get replace-vs-split-join-vs-replaceall 67 47 | ``` 48 | 49 | Preview a test: 50 | 51 | ```sh 52 | jsperf preview replace-vs-split-join-vs-replaceall 67 53 | ``` 54 | 55 | Run a test: 56 | 57 | ```sh 58 | jsperf run replace-vs-split-join-vs-replaceall 67 59 | ``` 60 | 61 | Results: 62 | 63 | ![jsperf results](https://raw.githubusercontent.com/OrKoN/jsperf/master/results.png "jsperf results") 64 | 65 | ## License 66 | 67 | [LICENSE](https://raw.githubusercontent.com/OrKoN/jsperf/master/LICENSE) 68 | -------------------------------------------------------------------------------- /bin/jsperf.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var argv = require('minimist')(process.argv.slice(2)); 4 | var command = argv._[0]; 5 | var testCase = argv._[1]; 6 | var revision = argv._[2]; 7 | var host = argv.host; 8 | var run = require('../lib/run'); 9 | var JsPerf = require('../lib/jsperf'); 10 | var fs = require('fs'); 11 | 12 | function printUsage() { 13 | console.log('Usage: '); 14 | console.log(' `jsperf init` - initialize the current directory for performance tests'); 15 | console.log(' `jsperf get [--host https://jsperf.com]` - download a test revision from jsperf.com'); 16 | console.log(' `jsperf preview [--host https://jsperf.com]` - output a fetched test revision'); 17 | console.log(' `jsperf run [--host https://jsperf.com]` - run a test revision'); 18 | } 19 | 20 | switch (command) { 21 | case 'init': 22 | fs.writeFileSync('./package.json', ` 23 | { 24 | "name": "jsperf.com", 25 | "version": "0.1.0", 26 | "private": true, 27 | "description": "Run jsperf.com tests locally with your NodeJS version", 28 | "dependencies": { 29 | }, 30 | "devDependencies": { 31 | } 32 | } 33 | `); 34 | run('npm install benchmark beautify-benchmark --save-dev'); 35 | break; 36 | case 'get': 37 | var jsPerf = new JsPerf(testCase, revision.toString(), { 38 | host: host, 39 | }); 40 | jsPerf 41 | .get() 42 | .then(() => console.log(`Test case ${testCase}v${revision} installed`)) 43 | .catch(err => console.error(`Failed to install test case ${testCase}v${revision}`, err)); 44 | break; 45 | case 'run': 46 | var jsPerf = new JsPerf(testCase, revision.toString()); 47 | jsPerf.run(); 48 | break; 49 | case 'preview': 50 | var jsPerf = new JsPerf(testCase, revision.toString()); 51 | console.log(jsPerf.getTestSource()); 52 | break; 53 | default: 54 | printUsage(); 55 | } 56 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // Settings 3 | "passfail": false, // Stop on first error. 4 | "maxerr": 100, // Maximum error before stopping. 5 | 6 | 7 | // Predefined globals whom JSHint will ignore. 8 | "browser": true, // Standard browser globals e.g. `window`, `document`. 9 | "node": true, 10 | 11 | 12 | // Development. 13 | "debug": false, // Allow debugger statements e.g. browser breakpoints. 14 | "devel": true, // Allow developments statements e.g. `console.log();`. 15 | 16 | 17 | // ECMAScript next 18 | "strict": false, // Require `use strict` pragma in every file. 19 | "globalstrict": false, // Allow global "use strict" (also enables 'strict'). 20 | "esnext": true, // ES6 syntax 21 | "futurehostile": true, // Warn about use of future language keywords. 22 | "predef": [ "-Promise", "Promise" ], // Allow me to redefine native Promise 23 | 24 | 25 | // The Good Parts. 26 | "asi": true, // Tolerate Automatic Semicolon Insertion (no semicolons). 27 | "laxbreak": true, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons. 28 | "bitwise": true, // Prohibit bitwise operators (&, |, ^, etc.). 29 | "boss": false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments. 30 | "eqeqeq": true, // Require triple equals i.e. `===`. 31 | "eqnull": false, // Tolerate use of `== null`. 32 | "evil": false, // Tolerate use of `eval`. 33 | "expr": false, // Tolerate `ExpressionStatement` as Programs. 34 | "forin": false, // Tolerate `for in` loops without `hasOwnPrototype`. 35 | "immed": true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` 36 | "latedef": true, // Prohipit variable use before definition. 37 | "loopfunc": false, // Allow functions to be defined within loops. 38 | "noarg": true, // Prohibit use of `arguments.caller` and `arguments.callee`. 39 | "regexp": true, // Prohibit `.` and `[^...]` in regular expressions. 40 | "regexdash": false, // Tolerate unescaped last dash i.e. `[-...]`. 41 | "scripturl": true, // Tolerate script-targeted URLs. 42 | "shadow": false, // Allows re-define variables later in code e.g. `var x=1; x=2;`. 43 | "supernew": false, // Tolerate `new function () { ... };` and `new Object;`. 44 | "undef": true, // Require all non-global variables be declared before they are used. 45 | 46 | 47 | // Personal styling preferences. 48 | "freeze": true, // Prohibit redefinition of native prototypes. 49 | "nonew": true, // Prohibit use of constructors for side-effects. 50 | "plusplus": false // Prohibit use of `++` & `--`. 51 | } 52 | -------------------------------------------------------------------------------- /lib/jsperf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var request = require('request'); 4 | var cheerio = require('cheerio'); 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | var mkdirp = require('mkdirp'); 8 | var beautify = require('js-beautify').js_beautify; 9 | var run = require('./run'); 10 | var jsStringEscape = require('js-string-escape'); 11 | 12 | class JsPerf { 13 | 14 | /** 15 | @public 16 | */ 17 | constructor(testCase, revision, opts) { 18 | opts = opts || {}; 19 | this.path = path.normalize(JsPerf.dir(opts.dir)); 20 | this.host = JsPerf.host(opts.host); 21 | this.testCase = testCase; 22 | this.revision = revision; 23 | try { 24 | fs.mkdirSync(this.path); 25 | } catch (err) {} 26 | } 27 | 28 | /** 29 | @public 30 | */ 31 | getTestSource() { 32 | let content = fs.readFileSync(this.getTestPath(), 'utf-8'); 33 | return content; 34 | } 35 | 36 | /** 37 | @public 38 | */ 39 | get() { 40 | return this 41 | .fetch() 42 | .then(test => this.store(test)); 43 | } 44 | 45 | /** 46 | @public 47 | */ 48 | run() { 49 | var path = this.getTestPath(); 50 | return run(`node ${path}`); 51 | } 52 | 53 | /** 54 | the rest is the private API 55 | @private 56 | */ 57 | static getScript(page) { 58 | var $ = cheerio.load(page); 59 | var scripts = $('script'); 60 | var len = scripts.length; 61 | var tests = []; 62 | var setup = `Benchmark.prototype.setup = '{actualSetup}';`; 63 | for (var i = len - 1; i >= 0 ; i--) { 64 | if ($(scripts[i]).attr('src')) { 65 | break; 66 | } 67 | var fragment = $(scripts[i]).text(); 68 | if (fragment.indexOf('ui.add') !== -1) { 69 | tests.push(fragment); 70 | } else if (fragment.indexOf('Benchmark.prototype.setup') !== -1) { 71 | setup = fragment; 72 | } else { 73 | setup = setup.replace(/\{actualSetup\}/, jsStringEscape(fragment) + '{actualSetup}'); 74 | } 75 | } 76 | setup = setup.replace(/\{actualSetup\}/, ''); 77 | return { tests: tests.join('\n'), setup: setup}; 78 | } 79 | 80 | /** 81 | @private 82 | */ 83 | static host(host) { 84 | return host || 'https://jsperf.com'; 85 | } 86 | 87 | /** 88 | @private 89 | */ 90 | static dir(dir) { 91 | return dir || './jsperf.com/'; 92 | } 93 | 94 | /** 95 | @private 96 | */ 97 | fetch() { 98 | const url = `${this.host}/${this.testCase}/${this.revision}`; 99 | return new Promise((resolve, reject) => { 100 | request(url, (error, response, body) => { 101 | if (error) { 102 | reject(error); 103 | } 104 | resolve(JsPerf.getScript(body)); 105 | }); 106 | }); 107 | } 108 | 109 | /** 110 | @private 111 | */ 112 | store(testData) { 113 | let dir = path.join(this.path, this.testCase, this.revision); 114 | let file = path.join(dir, 'test.js'); 115 | mkdirp.sync(dir); 116 | fs.writeFileSync(file, this.wrapTest(testData), 'utf-8'); 117 | } 118 | 119 | wrapTest(test) { 120 | test.tests = test.tests.replace(/\.add\(''/, '.add(\'default\''); 121 | return beautify(`var Benchmark = require('benchmark'); 122 | var ui = new Benchmark.Suite(); 123 | var benchmarks = require('beautify-benchmark') 124 | ui.browserscope = {}; 125 | var errors = false; 126 | ${test.setup} 127 | ${test.tests} 128 | // add listeners 129 | ui.on('cycle', function(event) { 130 | benchmarks.add(event.target) 131 | }) 132 | ui.on('complete', function() { 133 | if (!errors) { 134 | benchmarks.log() 135 | } else { 136 | console.error('Errors encountered. Check the source.'); 137 | process.exit(1); 138 | } 139 | }) 140 | ui.on('error', function(event) { 141 | errors =true; 142 | console.error(event.target.name, event.target.error); 143 | }) 144 | ui.run({ 'async': true }) 145 | `); 146 | } 147 | 148 | /** 149 | @private 150 | */ 151 | getTestPath() { 152 | return path.join(this.path, this.testCase, this.revision, 'test.js'); 153 | } 154 | } 155 | 156 | module.exports = JsPerf; 157 | -------------------------------------------------------------------------------- /test/test-page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | .replace() vs. .split().join() vs .replaceAll() · jsPerf 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |

.replace() vs. .split().join() vs .replaceAll()

17 |

JavaScript performance comparison

18 |
19 |

Revision 67 of this test case created by taheta

20 |
21 |

Info

22 |

A solution for replacement of strings.

23 |
24 |
25 |

Preparation code

26 |
 
<script>
Benchmark.prototype.setup = function() {
    var mystring = 'okay this is a long long long long string';
    var regexObj = new RegExp(".","gm");
    var regexShort = /\./g;
};
</script>
27 |
28 |
29 |

Test runner

30 |

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

31 |

Java applet disabled.

32 |

33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
Testing in Chrome 46.0.2490.86
TestOps/sec
.replace(/ /g,' ')
result = mystring.replace(/ /g,'');
pending…
.replace(/ /g,' ')
result = mystring.replace(/\s+/g,"");
pending…
.split(' ').join('')
result = mystring.split(' ').join('');
pending…
.split(/\s+/g).join('')
result = mystring.split(/\s+/g).join('');
pending…
.split(/ /g).join('')
result = mystring.split(/ /g).join('');
pending…
.replace(" ","")
result = mystring.replace(" ","");
pending…
75 |
76 |
77 |

Compare results of other browsers

78 |
79 |
80 |
81 |

Revisions

82 |

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

83 | 140 |
141 |

0 comments

142 |

Comment form temporarily disabled.

143 |
144 |
145 |

Add a comment

146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 | 158 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /test/test-page2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | RegExp.test vs. String.search · jsPerf 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |

RegExp.test vs. String.search

17 |

JavaScript performance comparison

18 |
19 |

Revision 24 of this test case created

20 |
21 |

Info

22 |

Check performance of simple regex use case of seeing whether a candidate string matches a regex.

23 |
24 |
25 |

Preparation code

26 |
<script>
  var strings = [];
  for (var i = 0; i < 500; ++i) {
    strings[i] = "The end of the beginning? Or the beginning of the end?";
  }

  var regex1 = /beginning/,
      regex2 = /(end of).*(end)/,
      regex3 = /beginnizg/;

  var simpleWrapper = function (regex, string) {
     string.search(regex) > -1;
  };
</script>
27 |
28 |
29 |

Test runner

30 |

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

31 |

Java applet disabled.

32 |

33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 |
Testing in Chrome 46.0.2490.86
TestOps/sec
String.search
var list = [];
for (var i = 0; i < strings.length; ++i) {
  var b = strings[i].search(regex1) > -1,
      b2 = strings[i].search(regex2) > -1,
      b3 = strings[i].search(regex3) > -1;
  list.push(b || b2 || b3);
}
pending…
String.match
var list = [];
for (var i = 0; i < strings.length; ++i) {
  var b = strings[i].match(regex1) !== null,
      b2 = strings[i].match(regex2) !== null,
      b3 = strings[i].match(regex3) !== null;
  list.push(b || b2 || b3);
}
pending…
RegExp.test
var list = [];
for (var i = 0; i < strings.length; ++i) {
  var b = regex1.test(strings[i]),
      b2 = regex2.test(strings[i]),
      b3 = regex3.test(strings[i]);
  list.push(b || b2 || b3);
}
pending…
RegExp.exec
var list = [];
for (var i = 0; i < strings.length; ++i) {
  var b = regex1.exec(strings[i]) !== null,
      b2 = regex2.exec(strings[i]) !== null,
      b3 = regex3.exec(strings[i]) !== null;
  list.push(b || b2 || b3);
}
pending…
String.search wrapped
var list = [];
for (var i = 0; i < strings.length; ++i) {
  var b = simpleWrapper(regex1, strings[i]),
      b2 = simpleWrapper(regex2, strings[i]),
      b3 = simpleWrapper(regex3, strings[i]);
  list.push(b || b2 || b3);
}
pending…
70 |
71 |
72 |

Compare results of other browsers

73 |
74 |
75 |
76 |

Revisions

77 |

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

78 | 97 |
98 |

0 comments

99 |

Comment form temporarily disabled.

100 |
101 |
102 |

Add a comment

103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | 115 | 164 | 165 | 179 | 180 | --------------------------------------------------------------------------------