Move bench results to own folder and generate per-test graphs

This commit is contained in:
Wilson Lin 2020-01-19 01:14:30 +11:00
parent 3e7072c7e4
commit e683eed0b5
12 changed files with 236 additions and 149 deletions

View File

@ -17,7 +17,7 @@ Available as:
Speed and effectiveness of Node.js version compared to [html-minfier](https://github.com/kangax/html-minifier) and [minimize](https://github.com/Swaagie/minimize). See [bench](./bench) folder for more details. Speed and effectiveness of Node.js version compared to [html-minfier](https://github.com/kangax/html-minifier) and [minimize](https://github.com/Swaagie/minimize). See [bench](./bench) folder for more details.
<img width="435" alt="Chart showing speed of HTML minifiers" src="./bench/speed.png"> <img width="435" alt="Chart showing effectiveness of HTML minifiers" src="./bench/minification.png"> <img width="435" alt="Chart showing speed of HTML minifiers" src="./bench/results/average-speeds.png"> <img width="435" alt="Chart showing effectiveness of HTML minifiers" src="./bench/results/average-sizes.png">
## Usage ## Usage

View File

@ -1,9 +1,9 @@
const benchmark = require('benchmark'); const benchmark = require('benchmark');
const childProcess = require('child_process'); const childProcess = require('child_process');
const fs = require('fs');
const minimist = require('minimist'); const minimist = require('minimist');
const path = require('path'); const path = require('path');
const programs = require('./minifiers'); const minifiers = require('./minifiers');
const results = require('./results');
const tests = require('./tests'); const tests = require('./tests');
const args = minimist(process.argv.slice(2)); const args = minimist(process.argv.slice(2));
@ -59,24 +59,24 @@ const setSize = (program, test, result) => {
// Run once to set sizes. // Run once to set sizes.
for (const t of tests) { for (const t of tests) {
for (const p of Object.keys(programs)) { for (const m of Object.keys(minifiers)) {
try { try {
setSize(p, t.name, programs[p](t.contentAsString, t.contentAsBuffer).length); setSize(m, t.name, minifiers[m](t.contentAsString, t.contentAsBuffer).length);
} catch (err) { } catch (err) {
console.error(`Failed to run ${p} on test ${t.name}:`); console.error(`Failed to run ${m} on test ${t.name}:`);
console.error(err); console.error(err);
process.exit(1); process.exit(1);
} }
} }
} }
fs.writeFileSync(path.join(__dirname, 'minification.json'), JSON.stringify(sizes, null, 2)); results.writeSizeResults(sizes);
const runTest = test => new Promise((resolve, reject) => { const runTest = test => new Promise((resolve, reject) => {
// Run JS libraries. // Run JS libraries.
const suite = new benchmark.Suite(); const suite = new benchmark.Suite();
for (const p of Object.keys(programs)) { for (const m of Object.keys(minifiers)) {
suite.add(p, () => { suite.add(m, () => {
programs[p](test.contentAsString, test.contentAsBuffer); minifiers[m](test.contentAsString, test.contentAsBuffer);
}); });
} }
suite suite
@ -87,7 +87,7 @@ const runTest = test => new Promise((resolve, reject) => {
}); });
(async () => { (async () => {
const results = fromEntries(tests.map(t => [t.name, {}])); const speeds = fromEntries(tests.map(t => [t.name, {}]));
// Run Rust library. // Run Rust library.
if (shouldRunRust) { if (shouldRunRust) {
@ -96,12 +96,12 @@ const runTest = test => new Promise((resolve, reject) => {
'--iterations', 512, '--iterations', 512,
'--tests', path.join(__dirname, 'tests'), '--tests', path.join(__dirname, 'tests'),
))) { ))) {
Object.assign(results[testName], {hyperbuild: testOps}); Object.assign(speeds[testName], {hyperbuild: testOps});
} }
} }
for (const t of tests) { for (const t of tests) {
Object.assign(results[t.name], await runTest(t)); Object.assign(speeds[t.name], await runTest(t));
} }
fs.writeFileSync(path.join(__dirname, 'speed.json'), JSON.stringify(results, null, 2)); results.writeSpeedResults(speeds);
})(); })();

View File

@ -1,7 +1,5 @@
const chartjs = require('chartjs-node'); const chartjs = require('chartjs-node');
const fs = require('fs'); const results = require('./results');
const path = require('path');
const tests = require('./tests');
const colours = { const colours = {
'hyperbuild': '#041f60', 'hyperbuild': '#041f60',
@ -10,8 +8,6 @@ const colours = {
'html-minifier': '#2ca02c', 'html-minifier': '#2ca02c',
}; };
const programNames = ['minimize', 'html-minifier', 'hyperbuild-nodejs'];
const chartOptions = (title, displayLegend, yTick = t => t) => ({ const chartOptions = (title, displayLegend, yTick = t => t) => ({
options: { options: {
title: { title: {
@ -45,67 +41,73 @@ const chartOptions = (title, displayLegend, yTick = t => t) => ({
legend: { legend: {
display: displayLegend, display: displayLegend,
labels: { labels: {
fontFamily: 'Arial, sans-serif',
fontColor: '#000', fontColor: '#000',
}, },
}, },
}, },
}); });
const renderChart = async (file, cfg) => { const renderChart = async (cfg) => {
const chart = new chartjs(900, 650); const chart = new chartjs(900, 650);
await chart.drawChart(cfg); await chart.drawChart(cfg);
await chart.writeImageToFile('image/png', path.join(__dirname, `${file}.png`)); return chart.getImageBuffer('image/png');
}; };
(async () => { (async () => {
const testNames = tests.map(t => t.name).sort(); const averageSpeeds = results.getSpeedResults().getAverageRelativeSpeedPerMinifier('hyperbuild-nodejs');
results.writeAverageSpeedsGraph(await renderChart({
const speedResults = JSON.parse(fs.readFileSync(path.join(__dirname, 'speed.json'), 'utf8'));
const speedData = programNames.map(program => [
program,
testNames
// Get OP/s for each test.
.map(test => speedResults[test][program] / speedResults[test]['hyperbuild-nodejs'])
// Sum all test OP/s.
.reduce((sum, c) => sum + c)
// Divide by tests count to get average OP/s.
/ testNames.length,
]).sort((a, b) => a[1] - b[1]);
await renderChart('speed', {
type: 'bar', type: 'bar',
data: { data: {
labels: speedData.map(([n]) => n), labels: averageSpeeds.map(([n]) => n),
// Node.js version is close enough to Rust version, so leave out Rust results.
// Include it this if this situation changes.
datasets: [{ datasets: [{
label: 'Average relative OP/s', label: 'Average relative OP/s',
backgroundColor: '#1f77b4', backgroundColor: '#1f77b4',
data: speedData.map(([_, v]) => v), data: averageSpeeds.map(([_, v]) => v),
}], }],
}, },
...chartOptions('Average operations per second (higher is better)', false, tick => `${tick * 100}%`), ...chartOptions('Average operations per second (higher is better)', false, tick => `${tick * 100}%`),
}); }));
const sizes = JSON.parse(fs.readFileSync(path.join(__dirname, 'minification.json'), 'utf8')); const speeds = results.getSpeedResults().getRelativeFileSpeedsPerMinifier('hyperbuild-nodejs');
const sizeData = programNames.map(program => [ results.writeSpeedsGraph(await renderChart({
program, type: 'bar',
testNames data: {
.map(test => sizes[test][program].relative) labels: speeds[0][1].map(([n]) => n),
.reduce((sum, c) => sum + c) datasets: speeds.map(([minifier, fileSpeeds]) => ({
/ testNames.length, label: minifier,
]).sort((a, b) => b[1] - a[1]); backgroundColor: colours[minifier],
await renderChart('minification', { data: fileSpeeds.map(([_, speed]) => speed),
})),
},
...chartOptions('Operations per second (higher is better)', true, tick => `${tick * 100}%`),
}));
const averageSizes = results.getSizeResults().getAverageRelativeSizePerMinifier();
results.writeAverageSizesGraph(await renderChart({
type: 'bar', type: 'bar',
scaleFontColor: 'red', scaleFontColor: 'red',
data: { data: {
labels: sizeData.map(([n]) => n), labels: averageSizes.map(([n]) => n),
datasets: [{ datasets: [{
label: 'Average minified size', label: 'Average minified size',
backgroundColor: '#2ca02c', backgroundColor: '#2ca02c',
data: sizeData.map(([_, v]) => v), data: averageSizes.map(([_, v]) => v),
}], }],
}, },
...chartOptions('Average minified size (lower is better)', false, tick => `${tick * 100}%`), ...chartOptions('Average minified size (lower is better)', false, tick => `${tick * 100}%`),
}); }));
const sizes = results.getSizeResults().getRelativeFileSizesPerMinifier();
results.writeSizesGraph(await renderChart({
type: 'bar',
data: {
labels: sizes[0][1].map(([n]) => n),
datasets: sizes.map(([minifier, fileSizes]) => ({
label: minifier,
backgroundColor: colours[minifier],
data: fileSizes.map(([_, size]) => size),
})),
},
...chartOptions('Minified size (lower is better)', true, tick => `${tick * 100}%`),
}));
})(); })();

85
bench/results.js Normal file
View File

@ -0,0 +1,85 @@
const {join} = require('path');
const {readFileSync, writeFileSync} = require('fs');
const minifiers = require('./minifiers');
const tests = require('./tests');
const RESULTS_DIR = join(__dirname, 'results');
const SPEEDS_JSON = join(RESULTS_DIR, 'speeds.json');
const SPEEDS_GRAPH = join(RESULTS_DIR, 'speeds.png');
const AVERAGE_SPEEDS_GRAPH = join(RESULTS_DIR, 'average-speeds.png');
const SIZES_JSON = join(RESULTS_DIR, 'sizes.json');
const SIZES_GRAPH = join(RESULTS_DIR, 'sizes.png');
const AVERAGE_SIZES_GRAPH = join(RESULTS_DIR, 'average-sizes.png');
const minifierNames = Object.keys(minifiers);
const testNames = tests.map(t => t.name);
module.exports = {
writeSpeedResults(speeds) {
writeFileSync(SPEEDS_JSON, JSON.stringify(speeds, null, 2));
},
writeSizeResults(sizes) {
writeFileSync(SIZES_JSON, JSON.stringify(sizes, null, 2));
},
writeAverageSpeedsGraph(data) {
writeFileSync(AVERAGE_SPEEDS_GRAPH, data);
},
writeSpeedsGraph(data) {
writeFileSync(SPEEDS_GRAPH, data);
},
writeAverageSizesGraph(data) {
writeFileSync(AVERAGE_SIZES_GRAPH, data);
},
writeSizesGraph(data) {
writeFileSync(SIZES_GRAPH, data);
},
getSpeedResults() {
const data = JSON.parse(readFileSync(SPEEDS_JSON, 'utf8'));
return {
// Get minifier-speed pairs sorted by speed ascending.
getAverageRelativeSpeedPerMinifier(baselineMinifier) {
return minifierNames.map(minifier => [
minifier,
testNames
// Get OP/s for each test.
.map(test => data[test][minifier] / data[test][baselineMinifier])
// Sum all test OP/s.
.reduce((sum, c) => sum + c)
// Divide by tests count to get average OP/s.
/ testNames.length,
]).sort((a, b) => a[1] - b[1]);
},
// Get minifier-speeds pairs.
getRelativeFileSpeedsPerMinifier(baselineMinifier) {
return minifierNames.map(minifier => [
minifier,
testNames.map(test => [test, data[test][minifier] / data[test][baselineMinifier]]),
]);
},
};
},
getSizeResults() {
const data = JSON.parse(readFileSync(SIZES_JSON, 'utf8'));
return {
// Get minifier-size pairs sorted by size descending.
getAverageRelativeSizePerMinifier() {
return minifierNames.map(minifier => [
minifier,
testNames
.map(test => data[test][minifier].relative)
.reduce((sum, c) => sum + c)
/ testNames.length,
]).sort((a, b) => b[1] - a[1]);
},
// Get minifier-sizes pairs.
getRelativeFileSizesPerMinifier() {
return minifierNames.map(minifier => [
minifier,
testNames.map(test => [test, data[test][minifier].relative]),
]);
},
};
},
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -5,8 +5,8 @@
"relative": 1 "relative": 1
}, },
"hyperbuild-nodejs": { "hyperbuild-nodejs": {
"absolute": 353501, "absolute": 353722,
"relative": 0.9584752288403974 "relative": 0.9590744436250054
}, },
"html-minifier": { "html-minifier": {
"absolute": 355185, "absolute": 355185,
@ -23,8 +23,8 @@
"relative": 1 "relative": 1
}, },
"hyperbuild-nodejs": { "hyperbuild-nodejs": {
"absolute": 231706, "absolute": 231728,
"relative": 0.9410641832204925 "relative": 0.9411535352961006
}, },
"html-minifier": { "html-minifier": {
"absolute": 234306, "absolute": 234306,
@ -59,8 +59,8 @@
"relative": 1 "relative": 1
}, },
"hyperbuild-nodejs": { "hyperbuild-nodejs": {
"absolute": 270833, "absolute": 272551,
"relative": 0.8743825506389188 "relative": 0.8799291022851277
}, },
"html-minifier": { "html-minifier": {
"absolute": 277911, "absolute": 277911,
@ -77,8 +77,8 @@
"relative": 1 "relative": 1
}, },
"hyperbuild-nodejs": { "hyperbuild-nodejs": {
"absolute": 79806, "absolute": 80117,
"relative": 0.9430212222904949 "relative": 0.9466961289407761
}, },
"html-minifier": { "html-minifier": {
"absolute": 81446, "absolute": 81446,
@ -95,8 +95,8 @@
"relative": 1 "relative": 1
}, },
"hyperbuild-nodejs": { "hyperbuild-nodejs": {
"absolute": 5744290, "absolute": 5761075,
"relative": 0.9094262560459782 "relative": 0.9120836287948699
}, },
"html-minifier": { "html-minifier": {
"absolute": 5785725, "absolute": 5785725,
@ -113,8 +113,8 @@
"relative": 1 "relative": 1
}, },
"hyperbuild-nodejs": { "hyperbuild-nodejs": {
"absolute": 196577, "absolute": 196585,
"relative": 0.9963860307162046 "relative": 0.996426580161184
}, },
"html-minifier": { "html-minifier": {
"absolute": 196600, "absolute": 196600,
@ -131,8 +131,8 @@
"relative": 1 "relative": 1
}, },
"hyperbuild-nodejs": { "hyperbuild-nodejs": {
"absolute": 28154, "absolute": 28203,
"relative": 0.8275476911319479 "relative": 0.8289879780135798
}, },
"html-minifier": { "html-minifier": {
"absolute": 29086, "absolute": 29086,
@ -149,8 +149,8 @@
"relative": 1 "relative": 1
}, },
"hyperbuild-nodejs": { "hyperbuild-nodejs": {
"absolute": 1263682, "absolute": 1263691,
"relative": 0.9967251233205608 "relative": 0.9967322220416869
}, },
"html-minifier": { "html-minifier": {
"absolute": 1263150, "absolute": 1263150,
@ -167,8 +167,8 @@
"relative": 1 "relative": 1
}, },
"hyperbuild-nodejs": { "hyperbuild-nodejs": {
"absolute": 648004, "absolute": 648011,
"relative": 0.9945163810263407 "relative": 0.994527124192536
}, },
"html-minifier": { "html-minifier": {
"absolute": 647822, "absolute": 647822,
@ -185,8 +185,8 @@
"relative": 1 "relative": 1
}, },
"hyperbuild-nodejs": { "hyperbuild-nodejs": {
"absolute": 86693, "absolute": 87177,
"relative": 0.7724652273476552 "relative": 0.7767778381701699
}, },
"html-minifier": { "html-minifier": {
"absolute": 88366, "absolute": 88366,
@ -203,8 +203,8 @@
"relative": 1 "relative": 1
}, },
"hyperbuild-nodejs": { "hyperbuild-nodejs": {
"absolute": 272480, "absolute": 272916,
"relative": 0.8645931830152878 "relative": 0.8659766336457732
}, },
"html-minifier": { "html-minifier": {
"absolute": 266639, "absolute": 266639,
@ -221,8 +221,8 @@
"relative": 1 "relative": 1
}, },
"hyperbuild-nodejs": { "hyperbuild-nodejs": {
"absolute": 1319432, "absolute": 1320740,
"relative": 0.9350014704267072 "relative": 0.9359283707317765
}, },
"html-minifier": { "html-minifier": {
"absolute": 1327244, "absolute": 1327244,

BIN
bench/results/sizes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

67
bench/results/speeds.json Normal file
View File

@ -0,0 +1,67 @@
{
"Amazon": {
"hyperbuild-nodejs": 496.69767089636576,
"html-minifier": 36.22644969886744,
"minimize": 113.46511363852734
},
"BBC": {
"hyperbuild-nodejs": 530.7581308772171,
"html-minifier": 49.92474956429014,
"minimize": 160.83375271616242
},
"Bing": {
"hyperbuild-nodejs": 2143.2738405584682,
"html-minifier": 224.01168295253845,
"minimize": 548.4301545148764
},
"Bootstrap": {
"hyperbuild-nodejs": 271.9278016916176,
"html-minifier": 8.063475087253176,
"minimize": 22.267478295105477
},
"Coding Horror": {
"hyperbuild-nodejs": 1023.4279917437968,
"html-minifier": 48.701642742613835,
"minimize": 186.3127301413615
},
"ECMA-262": {
"hyperbuild-nodejs": 15.516204982806318,
"html-minifier": 0.44813616072549084,
"minimize": 1.3379096629237164
},
"Google": {
"hyperbuild-nodejs": 1828.0608850870312,
"html-minifier": 243.05811241714517,
"minimize": 575.3743754787441
},
"Hacker News": {
"hyperbuild-nodejs": 2098.1972805031955,
"html-minifier": 73.59734557807897,
"minimize": 271.03920934848253
},
"NY Times": {
"hyperbuild-nodejs": 264.01040398999527,
"html-minifier": 36.483800875972946,
"minimize": 86.74545622195224
},
"Reddit": {
"hyperbuild-nodejs": 402.9146825613306,
"html-minifier": 44.44689052201565,
"minimize": 124.65890370904272
},
"Stack Overflow": {
"hyperbuild-nodejs": 822.1712538713629,
"html-minifier": 40.65363487825213,
"minimize": 158.40795613979014
},
"Twitter": {
"hyperbuild-nodejs": 279.88535523093344,
"html-minifier": 36.231829473085334,
"minimize": 167.45777652008428
},
"Wikipedia": {
"hyperbuild-nodejs": 54.31371594883309,
"html-minifier": 2.808823542495513,
"minimize": 8.687326663074192
}
}

BIN
bench/results/speeds.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@ -1,17 +1,17 @@
const fs = require('fs'); const fs = require('fs');
const mkdirp = require('mkdirp'); const mkdirp = require('mkdirp');
const path = require('path'); const path = require('path');
const programs = require('./minifiers'); const minifiers = require('./minifiers');
const tests = require('./tests'); const tests = require('./tests');
for (const t of tests) { for (const t of tests) {
for (const p of Object.keys(programs)) { for (const m of Object.keys(minifiers)) {
try { try {
const minPath = path.join(__dirname, 'min', p, `${t.name}.html`); const minPath = path.join(__dirname, 'min', m, `${t.name}.html`);
mkdirp.sync(path.dirname(minPath)); mkdirp.sync(path.dirname(minPath));
fs.writeFileSync(minPath, programs[p](t.contentAsString, t.contentAsBuffer)); fs.writeFileSync(minPath, minifiers[m](t.contentAsString, t.contentAsBuffer));
} catch (err) { } catch (err) {
console.error(`Failed to run ${p} on test ${t.name}:`); console.error(`Failed to run ${m} on test ${t.name}:`);
console.error(err); console.error(err);
process.exit(1); process.exit(1);
} }

View File

@ -1,67 +0,0 @@
{
"Amazon": {
"hyperbuild-nodejs": 499.9428905642954,
"html-minifier": 36.3036627447208,
"minimize": 113.33527430279831
},
"BBC": {
"hyperbuild-nodejs": 534.5358584753244,
"html-minifier": 49.23362197831472,
"minimize": 161.96803168572117
},
"Bing": {
"hyperbuild-nodejs": 2137.27013270331,
"html-minifier": 223.08512226985184,
"minimize": 550.9181761277221
},
"Bootstrap": {
"hyperbuild-nodejs": 277.1391080206004,
"html-minifier": 8.043255283692064,
"minimize": 22.245439492019898
},
"Coding Horror": {
"hyperbuild-nodejs": 1096.4910673601032,
"html-minifier": 49.83595257976626,
"minimize": 188.32749988717788
},
"ECMA-262": {
"hyperbuild-nodejs": 16.200240897950334,
"html-minifier": 0.45522858062374655,
"minimize": 1.3356053866389666
},
"Google": {
"hyperbuild-nodejs": 1832.4626475818236,
"html-minifier": 242.1462398878334,
"minimize": 564.1884364813526
},
"Hacker News": {
"hyperbuild-nodejs": 2127.8084431041266,
"html-minifier": 74.78979361035866,
"minimize": 272.3995630011103
},
"NY Times": {
"hyperbuild-nodejs": 265.55362689112695,
"html-minifier": 37.146711151201565,
"minimize": 87.7133467873164
},
"Reddit": {
"hyperbuild-nodejs": 391.75000439723124,
"html-minifier": 45.067854272152125,
"minimize": 125.87983932864549
},
"Stack Overflow": {
"hyperbuild-nodejs": 818.3755008258345,
"html-minifier": 41.43093414076361,
"minimize": 159.71387801780298
},
"Twitter": {
"hyperbuild-nodejs": 274.57816497268476,
"html-minifier": 36.94949014023178,
"minimize": 168.81796573617953
},
"Wikipedia": {
"hyperbuild-nodejs": 54.852210553433345,
"html-minifier": 2.821530343574604,
"minimize": 8.66394750522524
}
}