jsbenchee
v0.1.5
Published
A helper for JS benchmarks in browser
Downloads
432
Maintainers
Readme
jsBenchee
Benchmark JS scripts in a browser to get realistic average execution times.
- What it does and doesn't do
- Features
- Limitations
- Security
- Usage
- API options/parameters
- Recommendations
- About the examples
- Todos
What it does and doesn't do
jsBenchee is designed for quick benchmark tests and performance comparisions with minimal setup. It won't give you nanosecond precision – in fact the best we can get is an accuracy around 0.1ms.
Not too impressive, however you can retrieve a quite realistic average timing result to compare performance across different browsers.
Features
- minimal setup include es module script or iife, define tests
- auto loading of external library dependencies – nested module imports are not supported yet
- versatile report output: render the (fully responsive) benchmark report directly or use the returned data for custom processing (e.g saving to database or include them in your app UI), Markdown and JSON export.
- should run even in more restrictive environments such as StackOverflow snippets, codepens, jsFiddles etc.
Limitations
jsBenchee deploys the native browser's performance
API. Since accuracy is by default limited by most browsers to prevent timing attacks – an accuracy of ~ 0.1ms is the best we can get. However we can still get quite realistic average timings to get a close approximation of the "absolute" execution time. Also this approximation should suffice for "faster-or-slower" comparisons and it also gives insights about cross-browser performance differences.
Security
Please only don't run it in production environments!!!
Although I tried my best to implement some security measures – in fact you can load pretty much any script. So please be aware of this fact an use it only for debugging in test server environments.
Usage
Sorry, Browser-only – you can't run this in node or deno – more on this later.
JS
- Add the script to your
<head>
<script src="https://cdn.jsdelivr.net/npm/jsbenchee@latest/dist/jsBenchee.min.js"></script>
- Define the benchmark options an run test/s
let options = {
tests: [
{
name: 'simple script',
scripts: [
`async function waitFor(ms = 100) {
return new Promise(resolve => {
setTimeout(() => resolve('waiting for' +ms+'ms'), ms);
});
}
(async () => {
let delay = await waitFor(15);
return delay;
})();`
],
},
],
};
// init benchmark
let benchmark = new jsBenchee(options);
The above example takes the minimal setup and renders a benchmark report directly to your test HTML page. We can refine the output behavior. See API options.
JS - ES module
Same as above, but make sure you add the "module" type to your script tag like so:
<script type="module">
// import jsbenchee main function
import {jsbenchee} from 'https://cdn.jsdelivr.net/npm/jsbenchee@0.1.1/+esm'
let options = {
tests: [
{
name: 'simple script',
scripts: [
`async function waitFor(ms = 100) {
return new Promise(resolve => {
setTimeout(() => resolve('waiting for' +ms+'ms'), ms);
});
}
(async () => {
let delay = await waitFor(15);
return delay;
})();`
],
},
],
};
// init benchmark
let benchmark = new jsBenchee(options);
</script>
API options/parameters
The benchmark options are passed to jsBenchee(options)
as an object.
Global settings
| parameter | type | default | effect |
|:--|--:|--:| --:|
| iterations | number | 25 | number of benchmark test: higher values will return more realistic average values |
| agentDetection | Boolean | false | adds agent info to the report table – browser sniffing is bad but in this case it makes sense |
| multitask | Boolean | true | Run multiple test scripts in parallel. This returns reports faster but also affects accuracy – especially for more complex/expensive function. Disable it if you need better accuracy. For lightweight functions keep it activated |
| returnVar | string | empty/disabled | Allows to include a variable to the report table. Foremost for debugging to check if the tested script returns the expected value |
| security | Boolean | true | adds some very, very basic security heuristics to prevent malicious code execution. In case it returns false-positives - disable it |
| render | Boolean | true | render the report table to the HTML DOM directly or not |
| target | string/CSS selector | empty | define a target element for the report to be appended |
| addCSS | Boolean | true | auto-load jsBenchee CSS for report tables. Optional - you can also include the CSS manually from dist/jsBenchee.css
or use your own stylesheets |
| includeColumns | array | empty/[] | define the columns for the report table. For instance you could exclude the timings columns like so includeColumns = ['name, average, lowest, highest ]
|
| tests | array | empty/[] | the actual scripts to benchmark - see next table |
Test script settings
You can add multiple scripts by adding items to the tests
array:
| property | type | effect | | :-- | --: | --: | | name | string | just to identify your script in the report table | | scripts | array | add multiple scripts: these can be selfcontained script chunks or external references/library dependancies (see examples) |
Example: multiple tests (compare)
<script type="module">
import jsbenchee from 'https://cdn.jsdelivr.net/npm/jsbenchee@latest/+esm'
let d = `M10000 10000h10000a10000 10000 0 1 1 -10000 -10000 z`;
let options = {
//define multiple tests
tests: [
{
name: 'fontello - svgpath',
scripts: [
`import svgpath from 'https://cdn.jsdelivr.net/npm/svgpath@2.6.0/+esm';
let d ='${d}';
let pathData = new svgpath(d);
let dNew = pathData.segments.map(com=>{return com.join(' ')}).join(' ')`
]
},
{
name: 'pathData polyfill',
scripts: [
`let d = '${d}';
let pathData = parsepathPolyfill(d);
let dNew = pathData.map(com=>{return \`\${com.type} \${com.values.join(' ')}\`}).join(' ');
function parsepathPolyfill(d) {
//create temporary path
let path = document.createElementNS('http://www.w3.org/2000/svg', 'path')
path.setAttribute('d', d);
let pathData = path.getPathData();
path.remove();
return pathData
}`,
`https://cdn.jsdelivr.net/npm/path-data-polyfill@1.0.7/path-data-polyfill.min.js`
]
},
{
name: 'svg path commander',
scripts: [
`let d = '${d}';
let pathData = SVGPathCommander.parsePathString(d);
let dNew = pathData.map(com=>{return com.join(' ')}).join(' ')`,
'https://cdn.jsdelivr.net/gh/thednp/svg-path-commander@refs/heads/master/dist/svg-path-commander.js'
]
},
],
// increase iterations for more realistic average results
iterations: 25,
// enable browser detection
agentDetection: true,
// target for report rendering
target: '#benchmarks',
// render/append report to DOM
render: true,
// keep basic security check
security: true,
// run all compared scripts in parallel
multitask: true,
// output variable result
returnVar: 'dNew',
// define columns for report
includeColumns: ['name', 'average', 'highest', 'lowest', 'value']
};
/**
* jsbenchee in async mode
* process the results as you like
*/
(async () => {
// wait for the benchmark results
let benchmark = await new jsBenchee(options);
console.log('Async Benchmark Results:', benchmark);
})();
</script>
Define script dependancies
Each test item in the tests
array accepts multiple script definitions. If a script starts with an URL – jsBenchee will automatically detect it as a dependency.
{
name: 'svg path commander',
scripts: [
// actual script to benchmark
`let d = '${d}';
let pathData = SVGPathCommander.parsePathString(d);
let dNew = pathData.map(com=>{return com.join(' ')}).join(' ')`,
// load dependancy
'https://cdn.jsdelivr.net/gh/thednp/svg-path-commander@refs/heads/master/dist/svg-path-commander.js'
]
},
Recommendations
- Always close DevTools – especially on Chromium-based browsers opended dev tools significantly affect the timing results
- Double check the stringified JS code chunks: Backticks need to be escaped: Make sure backtick strings using template literals are escaped like so (prepending a backslash before the backtick and the "$")
let dNew = pathData.map(com=>{return \`\${com.type} \${com.values.join(' ')}\`});
- disable "multitask" mode for better comparisons. While the multitask mode is convenient for rather lightweight function calls it also affects the overall processing load and therefore the timing results.
About the examples
All of the used SVG libraries are excellent and none of if by any means "sluggish". I chose this comparisons to provide a realistic use case with scripts that are actually pretty close performance-wise.
Todos
- testing
- testing
- testing
- testing
- better error detection returning more details
- adding better security measures