Generate benchmark graphs

This commit is contained in:
Wilson Lin 2020-01-02 01:18:38 +11:00
parent e7fc519f05
commit c9b0e7a50d
5 changed files with 153 additions and 68 deletions

View File

@ -1,6 +1,6 @@
# hyperbuild
A fast one-pass in-place HTML minifier written in Rust with advanced whitespace handling.
A fast one-pass in-place HTML minifier written in Rust with context-aware whitespace handling.
Available as:
- CLI for Windows, macOS, and Linux.
@ -13,6 +13,10 @@ Available as:
- No extra heap memory is allocated during processing, which increases performance.
- Context-aware whitespace handling allows maximum minification while retaining wanted spaces.
## Performance
![Chart showing speed of HTML minifiers](./bench/speed.png) ![Chart showing effectiveness of HTML minifiers](./bench/minification.png)
## Usage
### CLI

View File

@ -1,95 +1,174 @@
"use strict";
const fs = require("fs");
const path = require("path");
const benchmark = require("benchmark");
const chartjs = require('chartjs-node');
const fs = require("fs");
const htmlMinifier = require("html-minifier");
const minimize = require("minimize");
const hyperbuild = require("hyperbuild");
const minimize = require("minimize");
const path = require("path");
const tests_dir = path.join(__dirname, "tests");
const tests = fs.readdirSync(tests_dir).map(name => ({
const testsDir = path.join(__dirname, "tests");
const tests = fs.readdirSync(testsDir).map(name => ({
name,
content: fs.readFileSync(path.join(tests_dir, name), "utf8"),
content: fs.readFileSync(path.join(testsDir, name), "utf8"),
}));
const programs = {
'hyperbuild-nodejs': content => hyperbuild.minify(Buffer.from(content)),
'html-minifier': content => htmlMinifier.minify(content, {
caseSensitive: false,
collapseBooleanAttributes: true,
collapseInlineTagWhitespace: true,
collapseWhitespace: true,
conservativeWhitespace: false,
customEventAttributes: [],
decodeEntities: true,
html5: true,
ignoreCustomComments: [],
ignoreCustomFragments: [],
includeAutoGeneratedTags: true,
keepClosingSlash: false,
minifyCSS: false,
minifyJS: false,
minifyURLs: false,
preserveLineBreaks: false,
preventAttributesEscaping: false,
processConditionalComments: true,
processScripts: [],
removeAttributeQuotes: true,
removeComments: true,
removeEmptyAttributes: false,
removeEmptyElements: false,
removeOptionalTags: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
removeTagWhitespace: true,
sortAttributes: true,
sortClassName: true,
trimCustomFragments: false,
useShortDoctype: true,
}).length,
'minimize': content => new minimize().parse(content).length,
};
const colours = [
{
backgroundColor: '#9ad0f5',
borderColor: '#47aaec',
},
{
backgroundColor: '#ffb0c1',
borderColor: '#ff87a1',
},
{
backgroundColor: '#a4dfdf',
borderColor: '#4bc0c0',
},
];
const renderChart = async (file, cfg) => {
const chart = new chartjs(450, 300);
await chart.drawChart({
...cfg,
options: {
scales: {
xAxes: [{
barPercentage: 0.5,
gridLines: {
color: '#ccc',
},
ticks: {
fontColor: '#222',
},
}],
yAxes: [{
gridLines: {
color: '#666',
},
ticks: {
fontColor: '#222',
},
}],
},
legend: {
labels: {
fontFamily: 'Ubuntu, sans-serif',
fontColor: '#000',
},
},
},
});
await chart.writeImageToFile('image/png', path.join(__dirname, `${file}.png`));
};
const sizes = {};
const setSize = (program, test, result) => {
console.log(`Received result for ${program} - ${test}`);
if (!sizes[test]) {
sizes[test] = {
original: {
result: tests.find(t => t.name === test).content.length,
absolute: tests.find(t => t.name === test).content.length,
relative: 1,
},
};
}
const original = sizes[test].original.result;
const original = sizes[test].original.absolute;
sizes[test][program] = {
result: result,
difference: `${((result - original) / original * 100).toFixed(2)}%`,
absolute: result,
relative: result / original,
};
};
const htmlMinifierSettings = {
caseSensitive: false,
collapseBooleanAttributes: true,
collapseInlineTagWhitespace: true,
collapseWhitespace: true,
conservativeWhitespace: false,
customEventAttributes: [],
decodeEntities: true,
html5: true,
ignoreCustomComments: [],
ignoreCustomFragments: [],
includeAutoGeneratedTags: true,
keepClosingSlash: false,
minifyCSS: false,
minifyJS: false,
minifyURLs: false,
preserveLineBreaks: false,
preventAttributesEscaping: false,
processConditionalComments: true,
processScripts: [],
removeAttributeQuotes: true,
removeComments: true,
removeEmptyAttributes: false,
removeEmptyElements: false,
removeOptionalTags: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
removeTagWhitespace: true,
sortAttributes: true,
sortClassName: true,
trimCustomFragments: false,
useShortDoctype: true,
};
// Run once to set sizes.
for (const t of tests) {
for (const p of Object.keys(programs)) {
setSize(p, t.name, programs[p](t.content));
}
}
new benchmark.Suite()
.add("hyperbuild", () => {
const suite = new benchmark.Suite();
for (const p of Object.keys(programs)) {
suite.add(p, () => {
for (const t of tests) {
setSize("hyperbuild", t.name, hyperbuild.minify(Buffer.from(t.content)));
programs[p](t.content);
}
})
.add("html-minifier", () => {
for (const t of tests) {
setSize("html-minifier", t.name, htmlMinifier.minify(t.content, htmlMinifierSettings).length);
}
})
.add("minimize", () => {
for (const t of tests) {
setSize("minimize", t.name, new minimize().parse(t.content).length);
}
})
});
}
suite
.on('cycle', event => {
console.info(event.target.toString());
})
.on('complete', function () {
console.info(`Fastest is ${this.filter('fastest').map('name')}`);
Object.entries(sizes).forEach(([test, results]) => {
console.info(test);
console.table(results);
.on('complete', async function () {
const speedResults = this.map(b => ({
name: b.name,
count: b.count,
ops: b.hz,
})).sort((a, b) => a.hz - b.hz);
await renderChart('speed', {
type: 'bar',
data: {
labels: speedResults.map(r => r.name),
datasets: [{
label: 'Operations per second',
...colours[0],
data: speedResults.map(r => r.ops),
}],
},
});
const testNames = Object.keys(sizes);
const programNames = Object.keys(programs);
await renderChart('minification', {
type: 'bar',
scaleFontColor: 'red',
data: {
labels: testNames,
datasets: programNames.map((program, i) => ({
label: program,
...colours[i],
data: testNames.map(test => sizes[test][program].relative),
})),
},
});
})
.run({'async': true});

BIN
bench/minification.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -2,9 +2,11 @@
"private": true,
"dependencies": {
"benchmark": "2.1.4",
"chart.js": "^2.9.3",
"chartjs-node": "^1.7.1",
"html-minifier": "3.5.19",
"minimize": "2.2.0",
"hyperbuild": "file:../nodejs"
"hyperbuild": "file:../nodejs",
"minimize": "2.2.0"
},
"scripts": {
"start": "node bench.js"

BIN
bench/speed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB