diff --git a/README.md b/README.md index 628be85..02837b5 100644 --- a/README.md +++ b/README.md @@ -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. -Chart showing speed of HTML minifiers Chart showing effectiveness of HTML minifiers +Chart showing speed of HTML minifiers Chart showing effectiveness of HTML minifiers ## Usage diff --git a/bench/bench.js b/bench/bench.js index d1797b2..9aec8dc 100644 --- a/bench/bench.js +++ b/bench/bench.js @@ -1,9 +1,9 @@ const benchmark = require('benchmark'); const childProcess = require('child_process'); -const fs = require('fs'); const minimist = require('minimist'); const path = require('path'); -const programs = require('./minifiers'); +const minifiers = require('./minifiers'); +const results = require('./results'); const tests = require('./tests'); const args = minimist(process.argv.slice(2)); @@ -59,24 +59,24 @@ const setSize = (program, test, result) => { // Run once to set sizes. for (const t of tests) { - for (const p of Object.keys(programs)) { + for (const m of Object.keys(minifiers)) { 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) { - console.error(`Failed to run ${p} on test ${t.name}:`); + console.error(`Failed to run ${m} on test ${t.name}:`); console.error(err); 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) => { // Run JS libraries. const suite = new benchmark.Suite(); - for (const p of Object.keys(programs)) { - suite.add(p, () => { - programs[p](test.contentAsString, test.contentAsBuffer); + for (const m of Object.keys(minifiers)) { + suite.add(m, () => { + minifiers[m](test.contentAsString, test.contentAsBuffer); }); } suite @@ -87,7 +87,7 @@ const runTest = test => new Promise((resolve, reject) => { }); (async () => { - const results = fromEntries(tests.map(t => [t.name, {}])); + const speeds = fromEntries(tests.map(t => [t.name, {}])); // Run Rust library. if (shouldRunRust) { @@ -96,12 +96,12 @@ const runTest = test => new Promise((resolve, reject) => { '--iterations', 512, '--tests', path.join(__dirname, 'tests'), ))) { - Object.assign(results[testName], {hyperbuild: testOps}); + Object.assign(speeds[testName], {hyperbuild: testOps}); } } 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); })(); diff --git a/bench/graph.js b/bench/graph.js index ab43cfc..e938659 100644 --- a/bench/graph.js +++ b/bench/graph.js @@ -1,7 +1,5 @@ const chartjs = require('chartjs-node'); -const fs = require('fs'); -const path = require('path'); -const tests = require('./tests'); +const results = require('./results'); const colours = { 'hyperbuild': '#041f60', @@ -10,8 +8,6 @@ const colours = { 'html-minifier': '#2ca02c', }; -const programNames = ['minimize', 'html-minifier', 'hyperbuild-nodejs']; - const chartOptions = (title, displayLegend, yTick = t => t) => ({ options: { title: { @@ -45,67 +41,73 @@ const chartOptions = (title, displayLegend, yTick = t => t) => ({ legend: { display: displayLegend, labels: { - fontFamily: 'Arial, sans-serif', fontColor: '#000', }, }, }, }); -const renderChart = async (file, cfg) => { +const renderChart = async (cfg) => { const chart = new chartjs(900, 650); await chart.drawChart(cfg); - await chart.writeImageToFile('image/png', path.join(__dirname, `${file}.png`)); + return chart.getImageBuffer('image/png'); }; (async () => { - const testNames = tests.map(t => t.name).sort(); - - 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', { + const averageSpeeds = results.getSpeedResults().getAverageRelativeSpeedPerMinifier('hyperbuild-nodejs'); + results.writeAverageSpeedsGraph(await renderChart({ type: 'bar', data: { - labels: speedData.map(([n]) => n), - // Node.js version is close enough to Rust version, so leave out Rust results. - // Include it this if this situation changes. + labels: averageSpeeds.map(([n]) => n), datasets: [{ label: 'Average relative OP/s', backgroundColor: '#1f77b4', - data: speedData.map(([_, v]) => v), + data: averageSpeeds.map(([_, v]) => v), }], }, ...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 sizeData = programNames.map(program => [ - program, - testNames - .map(test => sizes[test][program].relative) - .reduce((sum, c) => sum + c) - / testNames.length, - ]).sort((a, b) => b[1] - a[1]); - await renderChart('minification', { + const speeds = results.getSpeedResults().getRelativeFileSpeedsPerMinifier('hyperbuild-nodejs'); + results.writeSpeedsGraph(await renderChart({ + type: 'bar', + data: { + labels: speeds[0][1].map(([n]) => n), + datasets: speeds.map(([minifier, fileSpeeds]) => ({ + label: minifier, + backgroundColor: colours[minifier], + 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', scaleFontColor: 'red', data: { - labels: sizeData.map(([n]) => n), + labels: averageSizes.map(([n]) => n), datasets: [{ label: 'Average minified size', backgroundColor: '#2ca02c', - data: sizeData.map(([_, v]) => v), + data: averageSizes.map(([_, v]) => v), }], }, ...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}%`), + })); })(); diff --git a/bench/results.js b/bench/results.js new file mode 100644 index 0000000..f5ee7bf --- /dev/null +++ b/bench/results.js @@ -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]), + ]); + }, + }; + }, +}; diff --git a/bench/minification.png b/bench/results/average-sizes.png similarity index 56% rename from bench/minification.png rename to bench/results/average-sizes.png index 2b84b37..8837be9 100644 Binary files a/bench/minification.png and b/bench/results/average-sizes.png differ diff --git a/bench/speed.png b/bench/results/average-speeds.png similarity index 51% rename from bench/speed.png rename to bench/results/average-speeds.png index 223a884..5fd95a6 100644 Binary files a/bench/speed.png and b/bench/results/average-speeds.png differ diff --git a/bench/minification.json b/bench/results/sizes.json similarity index 84% rename from bench/minification.json rename to bench/results/sizes.json index 15ac9d7..86387b3 100644 --- a/bench/minification.json +++ b/bench/results/sizes.json @@ -5,8 +5,8 @@ "relative": 1 }, "hyperbuild-nodejs": { - "absolute": 353501, - "relative": 0.9584752288403974 + "absolute": 353722, + "relative": 0.9590744436250054 }, "html-minifier": { "absolute": 355185, @@ -23,8 +23,8 @@ "relative": 1 }, "hyperbuild-nodejs": { - "absolute": 231706, - "relative": 0.9410641832204925 + "absolute": 231728, + "relative": 0.9411535352961006 }, "html-minifier": { "absolute": 234306, @@ -59,8 +59,8 @@ "relative": 1 }, "hyperbuild-nodejs": { - "absolute": 270833, - "relative": 0.8743825506389188 + "absolute": 272551, + "relative": 0.8799291022851277 }, "html-minifier": { "absolute": 277911, @@ -77,8 +77,8 @@ "relative": 1 }, "hyperbuild-nodejs": { - "absolute": 79806, - "relative": 0.9430212222904949 + "absolute": 80117, + "relative": 0.9466961289407761 }, "html-minifier": { "absolute": 81446, @@ -95,8 +95,8 @@ "relative": 1 }, "hyperbuild-nodejs": { - "absolute": 5744290, - "relative": 0.9094262560459782 + "absolute": 5761075, + "relative": 0.9120836287948699 }, "html-minifier": { "absolute": 5785725, @@ -113,8 +113,8 @@ "relative": 1 }, "hyperbuild-nodejs": { - "absolute": 196577, - "relative": 0.9963860307162046 + "absolute": 196585, + "relative": 0.996426580161184 }, "html-minifier": { "absolute": 196600, @@ -131,8 +131,8 @@ "relative": 1 }, "hyperbuild-nodejs": { - "absolute": 28154, - "relative": 0.8275476911319479 + "absolute": 28203, + "relative": 0.8289879780135798 }, "html-minifier": { "absolute": 29086, @@ -149,8 +149,8 @@ "relative": 1 }, "hyperbuild-nodejs": { - "absolute": 1263682, - "relative": 0.9967251233205608 + "absolute": 1263691, + "relative": 0.9967322220416869 }, "html-minifier": { "absolute": 1263150, @@ -167,8 +167,8 @@ "relative": 1 }, "hyperbuild-nodejs": { - "absolute": 648004, - "relative": 0.9945163810263407 + "absolute": 648011, + "relative": 0.994527124192536 }, "html-minifier": { "absolute": 647822, @@ -185,8 +185,8 @@ "relative": 1 }, "hyperbuild-nodejs": { - "absolute": 86693, - "relative": 0.7724652273476552 + "absolute": 87177, + "relative": 0.7767778381701699 }, "html-minifier": { "absolute": 88366, @@ -203,8 +203,8 @@ "relative": 1 }, "hyperbuild-nodejs": { - "absolute": 272480, - "relative": 0.8645931830152878 + "absolute": 272916, + "relative": 0.8659766336457732 }, "html-minifier": { "absolute": 266639, @@ -221,8 +221,8 @@ "relative": 1 }, "hyperbuild-nodejs": { - "absolute": 1319432, - "relative": 0.9350014704267072 + "absolute": 1320740, + "relative": 0.9359283707317765 }, "html-minifier": { "absolute": 1327244, diff --git a/bench/results/sizes.png b/bench/results/sizes.png new file mode 100644 index 0000000..3b4e58f Binary files /dev/null and b/bench/results/sizes.png differ diff --git a/bench/results/speeds.json b/bench/results/speeds.json new file mode 100644 index 0000000..f2f438e --- /dev/null +++ b/bench/results/speeds.json @@ -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 + } +} \ No newline at end of file diff --git a/bench/results/speeds.png b/bench/results/speeds.png new file mode 100644 index 0000000..2322f1c Binary files /dev/null and b/bench/results/speeds.png differ diff --git a/bench/run.js b/bench/run.js index a97294f..c8d3635 100644 --- a/bench/run.js +++ b/bench/run.js @@ -1,17 +1,17 @@ const fs = require('fs'); const mkdirp = require('mkdirp'); const path = require('path'); -const programs = require('./minifiers'); +const minifiers = require('./minifiers'); const tests = require('./tests'); for (const t of tests) { - for (const p of Object.keys(programs)) { + for (const m of Object.keys(minifiers)) { 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)); - fs.writeFileSync(minPath, programs[p](t.contentAsString, t.contentAsBuffer)); + fs.writeFileSync(minPath, minifiers[m](t.contentAsString, t.contentAsBuffer)); } 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); process.exit(1); } diff --git a/bench/speed.json b/bench/speed.json deleted file mode 100644 index 2c25fcf..0000000 --- a/bench/speed.json +++ /dev/null @@ -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 - } -} \ No newline at end of file