Also bench hyperbuild Rust; improve graphs; remove default values for html-minifier config
This commit is contained in:
parent
1cb9bf9817
commit
68c43ab486
|
@ -1,2 +1,4 @@
|
|||
node_modules/
|
||||
min/
|
||||
hyperbuild-bench/Cargo.lock
|
||||
hyperbuild-bench/target/
|
||||
|
|
163
bench/bench.js
163
bench/bench.js
|
@ -1,65 +1,39 @@
|
|||
"use strict";
|
||||
|
||||
const benchmark = require('benchmark');
|
||||
const chartjs = require('chartjs-node');
|
||||
const childProcess = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const programs = require('./minifiers');
|
||||
const tests = require('./tests');
|
||||
|
||||
const colours = [
|
||||
{
|
||||
backgroundColor: '#9ad0f5',
|
||||
borderColor: '#47aaec',
|
||||
},
|
||||
{
|
||||
backgroundColor: '#ffb0c1',
|
||||
borderColor: '#ff87a1',
|
||||
},
|
||||
{
|
||||
backgroundColor: '#a4dfdf',
|
||||
borderColor: '#4bc0c0',
|
||||
},
|
||||
];
|
||||
const chartOptions = (title, displayLegend, yTick = t => t) => ({
|
||||
options: {
|
||||
title: {
|
||||
display: true,
|
||||
text: title,
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
barPercentage: 0.5,
|
||||
gridLines: {
|
||||
color: '#ccc',
|
||||
},
|
||||
ticks: {
|
||||
fontColor: '#222',
|
||||
},
|
||||
}],
|
||||
yAxes: [{
|
||||
gridLines: {
|
||||
color: '#666',
|
||||
},
|
||||
ticks: {
|
||||
callback: yTick,
|
||||
fontColor: '#222',
|
||||
},
|
||||
}],
|
||||
},
|
||||
legend: {
|
||||
display: displayLegend,
|
||||
labels: {
|
||||
fontFamily: 'Ubuntu, sans-serif',
|
||||
fontColor: '#000',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const renderChart = async (file, cfg) => {
|
||||
const chart = new chartjs(435, 320);
|
||||
await chart.drawChart(cfg);
|
||||
await chart.writeImageToFile('image/png', path.join(__dirname, `${file}.png`));
|
||||
const cmd = (command, ...args) => {
|
||||
const throwErr = msg => {
|
||||
throw new Error(`${msg}\n ${command} ${args.join(' ')}`);
|
||||
};
|
||||
|
||||
const {status, signal, error, stdout, stderr} = childProcess.spawnSync(command, args.map(String), {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
encoding: 'utf8',
|
||||
});
|
||||
if (error) {
|
||||
throwErr(error.message);
|
||||
}
|
||||
if (signal) {
|
||||
throwErr(`Command exited with signal ${signal}`);
|
||||
}
|
||||
if (status !== 0) {
|
||||
throwErr(`Command exited with status ${status}`);
|
||||
}
|
||||
if (stderr) {
|
||||
throwErr(`stderr: ${stderr}`);
|
||||
}
|
||||
return stdout;
|
||||
};
|
||||
|
||||
const fromEntries = entries => {
|
||||
if (Object.fromEntries) return Object.fromEntries(entries);
|
||||
const obj = {};
|
||||
for (const [prop, val] of entries) obj[prop] = val;
|
||||
return obj;
|
||||
};
|
||||
|
||||
const sizes = {};
|
||||
|
@ -91,52 +65,37 @@ for (const t of tests) {
|
|||
}
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(path.join(__dirname, 'minification.json'), JSON.stringify(sizes, null, 2));
|
||||
|
||||
const suite = new benchmark.Suite();
|
||||
for (const p of Object.keys(programs)) {
|
||||
suite.add(p, () => {
|
||||
for (const t of tests) {
|
||||
programs[p](t.content);
|
||||
}
|
||||
});
|
||||
}
|
||||
suite
|
||||
.on('cycle', event => {
|
||||
console.info(event.target.toString());
|
||||
})
|
||||
.on('complete', async function () {
|
||||
const speedResults = this.map(b => ({
|
||||
name: b.name,
|
||||
ops: b.hz,
|
||||
}));
|
||||
fs.writeFileSync(path.join(__dirname, "speed.json"), JSON.stringify(speedResults, null, 2));
|
||||
await renderChart('speed', {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: speedResults.map(r => r.name),
|
||||
datasets: [{
|
||||
...colours[0],
|
||||
data: speedResults.map(r => r.ops),
|
||||
}],
|
||||
},
|
||||
...chartOptions('Operations per second (higher is better)', false),
|
||||
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.content);
|
||||
});
|
||||
}
|
||||
suite
|
||||
.on('cycle', event => console.info(test.name, event.target.toString()))
|
||||
.on('complete', () => resolve(fromEntries(suite.map(b => [b.name, b.hz]))))
|
||||
.on('error', reject)
|
||||
.run({'async': true});
|
||||
});
|
||||
|
||||
const testNames = Object.keys(sizes);
|
||||
const programNames = Object.keys(programs);
|
||||
fs.writeFileSync(path.join(__dirname, "minification.json"), JSON.stringify(sizes, null, 2));
|
||||
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 * 100),
|
||||
})),
|
||||
},
|
||||
...chartOptions('Relative minified HTML file size (lower is better)', true, tick => `${tick}%`),
|
||||
});
|
||||
})
|
||||
.run({'async': true});
|
||||
(async () => {
|
||||
const results = {};
|
||||
|
||||
// Run Rust library.
|
||||
for (const [testName, testOps] of JSON.parse(cmd(
|
||||
path.join(__dirname, 'hyperbuild-bench', 'target', 'release', 'hyperbuild-bench'),
|
||||
'--iterations', 100,
|
||||
'--tests', path.join(__dirname, 'tests'),
|
||||
))) {
|
||||
results[testName] = {hyperbuild: testOps};
|
||||
}
|
||||
|
||||
for (const t of tests) {
|
||||
Object.assign(results[t.name], await runTest(t));
|
||||
}
|
||||
fs.writeFileSync(path.join(__dirname, 'speed.json'), JSON.stringify(results, null, 2));
|
||||
})();
|
||||
|
|
|
@ -9,6 +9,7 @@ const tests = {
|
|||
"Bootstrap": "https://getbootstrap.com/docs/3.4/css/",
|
||||
"Bing": "https://www.bing.com/",
|
||||
"Coding Horror": "https://blog.codinghorror.com/",
|
||||
"ECMA-262": "https://www.ecma-international.org/ecma-262/10.0/index.html",
|
||||
"Google": "https://www.google.com/",
|
||||
"Hacker News": "https://news.ycombinator.com/",
|
||||
"NY Times": "https://www.nytimes.com/",
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
const chartjs = require('chartjs-node');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const tests = require('./tests');
|
||||
|
||||
const colours = {
|
||||
'hyperbuild': '#041f60',
|
||||
'hyperbuild-nodejs': '#0476d0',
|
||||
'minimize': '#3cacae',
|
||||
'html-minifier': '#5d6c89',
|
||||
};
|
||||
|
||||
const chartOptions = (title, displayLegend, yTick = t => t) => ({
|
||||
options: {
|
||||
title: {
|
||||
display: true,
|
||||
text: title,
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
barPercentage: 0.75,
|
||||
gridLines: {
|
||||
color: '#ccc',
|
||||
},
|
||||
ticks: {
|
||||
fontColor: '#222',
|
||||
},
|
||||
}],
|
||||
yAxes: [{
|
||||
gridLines: {
|
||||
color: '#666',
|
||||
},
|
||||
ticks: {
|
||||
callback: yTick,
|
||||
fontColor: '#222',
|
||||
},
|
||||
}],
|
||||
},
|
||||
legend: {
|
||||
display: displayLegend,
|
||||
labels: {
|
||||
fontFamily: 'Ubuntu, sans-serif',
|
||||
fontColor: '#000',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const renderChart = async (file, cfg) => {
|
||||
const chart = new chartjs(435, 320);
|
||||
await chart.drawChart(cfg);
|
||||
await chart.writeImageToFile('image/png', path.join(__dirname, `${file}.png`));
|
||||
};
|
||||
|
||||
(async () => {
|
||||
const testNames = tests.map(t => t.name).sort();
|
||||
|
||||
const speedResults = JSON.parse(fs.readFileSync(path.join(__dirname, 'speed.json'), 'utf8'));
|
||||
await renderChart('speed', {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: testNames,
|
||||
datasets: ['hyperbuild', 'hyperbuild-nodejs', 'minimize', 'html-minifier'].map(program => ({
|
||||
label: program,
|
||||
backgroundColor: colours[program],
|
||||
data: testNames.map(test => speedResults[test][program] / speedResults[test]['hyperbuild'] * 100),
|
||||
})),
|
||||
},
|
||||
...chartOptions('Relative operations per second (higher is better)', true, tick => `${tick}%`),
|
||||
});
|
||||
|
||||
const sizes = JSON.parse(fs.readFileSync(path.join(__dirname, 'minification.json'), 'utf8'));
|
||||
await renderChart('minification', {
|
||||
type: 'bar',
|
||||
scaleFontColor: 'red',
|
||||
data: {
|
||||
labels: testNames,
|
||||
datasets: ['hyperbuild-nodejs', 'html-minifier', 'minimize'].map(program => ({
|
||||
label: program,
|
||||
backgroundColor: colours[program],
|
||||
data: testNames.map(test => sizes[test][program].relative * 100),
|
||||
})),
|
||||
},
|
||||
...chartOptions('Relative minified HTML file size (lower is better)', true, tick => `${tick}%`),
|
||||
});
|
||||
})();
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "hyperbuild-bench"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
authors = ["Wilson Lin <code@wilsonl.in>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
hyperbuild = { path = "../.." }
|
||||
structopt = "0.3.5"
|
||||
serde = { version = "1.0.104", features = ["derive"] }
|
||||
serde_json = "1.0.44"
|
|
@ -0,0 +1,34 @@
|
|||
use hyperbuild::hyperbuild;
|
||||
use std::fs;
|
||||
use std::io::{stdout};
|
||||
use std::time::Instant;
|
||||
use structopt::StructOpt;
|
||||
|
||||
#[derive(StructOpt)]
|
||||
struct Args {
|
||||
#[structopt(long, parse(from_os_str))]
|
||||
tests: std::path::PathBuf,
|
||||
#[structopt(long)]
|
||||
iterations: usize,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::from_args();
|
||||
let tests = fs::read_dir(args.tests).unwrap().map(|d| d.unwrap());
|
||||
|
||||
let mut results: Vec<(String, f64)> = Vec::new();
|
||||
|
||||
for t in tests {
|
||||
let source = fs::read(t.path()).unwrap();
|
||||
let start = Instant::now();
|
||||
for _ in 0..args.iterations {
|
||||
let mut data = source.to_vec();
|
||||
hyperbuild(&mut data).unwrap();
|
||||
};
|
||||
let elapsed = start.elapsed().as_secs_f64();
|
||||
let ops = args.iterations as f64 / elapsed;
|
||||
results.push((t.file_name().to_str().unwrap().to_string(), ops));
|
||||
};
|
||||
|
||||
serde_json::to_writer(stdout(), &results).unwrap();
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
rm -rf node_modules
|
||||
HYPERBUILD_NODEJS_SKIP_BIN_DOWNLOAD=1 npm i
|
||||
pushd hyperbuild-bench
|
||||
cargo build --release
|
||||
popd
|
|
@ -9,8 +9,8 @@
|
|||
"relative": 0.6330862904281787
|
||||
},
|
||||
"html-minifier": {
|
||||
"absolute": 488839,
|
||||
"relative": 0.8534572912574746
|
||||
"absolute": 488822,
|
||||
"relative": 0.8534276111911309
|
||||
},
|
||||
"minimize": {
|
||||
"absolute": 495105,
|
||||
|
@ -45,8 +45,8 @@
|
|||
"relative": 0.6222239353466829
|
||||
},
|
||||
"html-minifier": {
|
||||
"absolute": 137061,
|
||||
"relative": 0.8805095688708154
|
||||
"absolute": 137026,
|
||||
"relative": 0.8802847212853573
|
||||
},
|
||||
"minimize": {
|
||||
"absolute": 137358,
|
||||
|
@ -63,8 +63,8 @@
|
|||
"relative": 0.7342292960872684
|
||||
},
|
||||
"html-minifier": {
|
||||
"absolute": 270621,
|
||||
"relative": 0.7325266961711803
|
||||
"absolute": 270604,
|
||||
"relative": 0.732480679957232
|
||||
},
|
||||
"minimize": {
|
||||
"absolute": 279252,
|
||||
|
@ -99,8 +99,8 @@
|
|||
"relative": 0.5906241489629755
|
||||
},
|
||||
"html-minifier": {
|
||||
"absolute": 383586,
|
||||
"relative": 0.9762867679809012
|
||||
"absolute": 383578,
|
||||
"relative": 0.9762664067212518
|
||||
},
|
||||
"minimize": {
|
||||
"absolute": 384579,
|
||||
|
@ -117,8 +117,8 @@
|
|||
"relative": 0.5157900380676639
|
||||
},
|
||||
"html-minifier": {
|
||||
"absolute": 28464,
|
||||
"relative": 0.5087126695619538
|
||||
"absolute": 28448,
|
||||
"relative": 0.5084267152788948
|
||||
},
|
||||
"minimize": {
|
||||
"absolute": 30643,
|
||||
|
@ -135,8 +135,8 @@
|
|||
"relative": 0.6978107321127253
|
||||
},
|
||||
"html-minifier": {
|
||||
"absolute": 1888047,
|
||||
"relative": 0.9521424184017114
|
||||
"absolute": 1887947,
|
||||
"relative": 0.9520919883849586
|
||||
},
|
||||
"minimize": {
|
||||
"absolute": 1888704,
|
||||
|
@ -153,8 +153,8 @@
|
|||
"relative": 0.5387307214917895
|
||||
},
|
||||
"html-minifier": {
|
||||
"absolute": 1115700,
|
||||
"relative": 0.7231445803045672
|
||||
"absolute": 1115617,
|
||||
"relative": 0.7230907835848708
|
||||
},
|
||||
"minimize": {
|
||||
"absolute": 1117526,
|
||||
|
@ -171,8 +171,8 @@
|
|||
"relative": 0.5613076908178878
|
||||
},
|
||||
"html-minifier": {
|
||||
"absolute": 89329,
|
||||
"relative": 0.5766919089212971
|
||||
"absolute": 89321,
|
||||
"relative": 0.5766402623645085
|
||||
},
|
||||
"minimize": {
|
||||
"absolute": 91363,
|
||||
|
@ -189,8 +189,8 @@
|
|||
"relative": 0.9003600363028295
|
||||
},
|
||||
"html-minifier": {
|
||||
"absolute": 273191,
|
||||
"relative": 0.9082057027356775
|
||||
"absolute": 273174,
|
||||
"relative": 0.9081491873418815
|
||||
},
|
||||
"minimize": {
|
||||
"absolute": 273887,
|
||||
|
@ -207,8 +207,8 @@
|
|||
"relative": 0.5520822745589214
|
||||
},
|
||||
"html-minifier": {
|
||||
"absolute": 1307604,
|
||||
"relative": 0.5359190926945385
|
||||
"absolute": 1307563,
|
||||
"relative": 0.5359022889200009
|
||||
},
|
||||
"minimize": {
|
||||
"absolute": 1368203,
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
@ -3,39 +3,24 @@ const hyperbuild = require("hyperbuild");
|
|||
const minimize = require("minimize");
|
||||
|
||||
module.exports = {
|
||||
'hyperbuild-nodejs': content => hyperbuild.minify(Buffer.from(content)),
|
||||
'hyperbuild-nodejs': content => hyperbuild.minify(content),
|
||||
'html-minifier': content => htmlMinifier.minify(content, {
|
||||
caseSensitive: false,
|
||||
collapseBooleanAttributes: true,
|
||||
collapseInlineTagWhitespace: true,
|
||||
collapseWhitespace: true,
|
||||
conservativeWhitespace: false,
|
||||
customEventAttributes: [],
|
||||
decodeEntities: true,
|
||||
html5: true,
|
||||
ignoreCustomComments: [],
|
||||
ignoreCustomFragments: [/<\?[\s\S]*?\?>/],
|
||||
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,
|
||||
removeEmptyAttributes: true,
|
||||
removeOptionalTags: true,
|
||||
removeRedundantAttributes: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
removeTagWhitespace: true,
|
||||
sortAttributes: false,
|
||||
sortClassName: false,
|
||||
trimCustomFragments: false,
|
||||
useShortDoctype: true,
|
||||
}),
|
||||
'minimize': content => new minimize().parse(content),
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
"request": "^2.88.0",
|
||||
"request-promise-native": "^1.0.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": "10.x"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node bench.js"
|
||||
}
|
||||
|
|
|
@ -1,14 +1,74 @@
|
|||
[
|
||||
{
|
||||
"name": "hyperbuild-nodejs",
|
||||
"ops": 10.158435796714862
|
||||
{
|
||||
"Amazon.html": {
|
||||
"hyperbuild": 245.46705260338564,
|
||||
"hyperbuild-nodejs": 145.21435374237635,
|
||||
"html-minifier": 16.19830761009811,
|
||||
"minimize": 95.71966364576267
|
||||
},
|
||||
{
|
||||
"name": "html-minifier",
|
||||
"ops": 0.9598865156998558
|
||||
"BBC.html": {
|
||||
"hyperbuild": 429.2291873495222,
|
||||
"hyperbuild-nodejs": 251.3721939160052,
|
||||
"html-minifier": 18.333446052847226,
|
||||
"minimize": 108.38902861455512
|
||||
},
|
||||
{
|
||||
"name": "minimize",
|
||||
"ops": 3.6888006698975837
|
||||
"Bootstrap.html": {
|
||||
"hyperbuild": 235.08368235051825,
|
||||
"hyperbuild-nodejs": 156.19542771462898,
|
||||
"html-minifier": 8.557266916672539,
|
||||
"minimize": 22.359774537863895
|
||||
},
|
||||
"Bing.html": {
|
||||
"hyperbuild": 1008.1262435363229,
|
||||
"hyperbuild-nodejs": 585.3489088472239,
|
||||
"html-minifier": 79.35385186294975,
|
||||
"minimize": 435.31581246812584
|
||||
},
|
||||
"Coding Horror.html": {
|
||||
"hyperbuild": 1146.867798530376,
|
||||
"hyperbuild-nodejs": 680.2295027510518,
|
||||
"html-minifier": 45.63362214760677,
|
||||
"minimize": 164.51899348138494
|
||||
},
|
||||
"Google.html": {
|
||||
"hyperbuild": 344.0346646025321,
|
||||
"hyperbuild-nodejs": 317.3708534283478,
|
||||
"html-minifier": 29.36827883130167,
|
||||
"minimize": 365.1698468973524
|
||||
},
|
||||
"Hacker News.html": {
|
||||
"hyperbuild": 1804.5683188361834,
|
||||
"hyperbuild-nodejs": 1259.6432378637871,
|
||||
"html-minifier": 66.43984413610241,
|
||||
"minimize": 255.30928557346104
|
||||
},
|
||||
"NY Times.html": {
|
||||
"hyperbuild": 123.84742876588177,
|
||||
"hyperbuild-nodejs": 51.83081525871115,
|
||||
"html-minifier": 7.334756953956464,
|
||||
"minimize": 59.400301132747934
|
||||
},
|
||||
"Reddit.html": {
|
||||
"hyperbuild": 109.45057921629598,
|
||||
"hyperbuild-nodejs": 66.80243904185947,
|
||||
"html-minifier": 6.3323721760167695,
|
||||
"minimize": 44.528247219895
|
||||
},
|
||||
"Stack Overflow.html": {
|
||||
"hyperbuild": 763.6540095978328,
|
||||
"hyperbuild-nodejs": 496.21357271825997,
|
||||
"html-minifier": 39.39722290667494,
|
||||
"minimize": 148.07292819104936
|
||||
},
|
||||
"Twitter.html": {
|
||||
"hyperbuild": 376.9341764747767,
|
||||
"hyperbuild-nodejs": 208.2611701306221,
|
||||
"html-minifier": 42.264558908660206,
|
||||
"minimize": 136.3651156178245
|
||||
},
|
||||
"Wikipedia.html": {
|
||||
"hyperbuild": 52.02792034641937,
|
||||
"hyperbuild-nodejs": 32.045431164840046,
|
||||
"html-minifier": 2.35238631274572,
|
||||
"minimize": 7.878943786969402
|
||||
}
|
||||
]
|
||||
}
|
BIN
bench/speed.png
BIN
bench/speed.png
Binary file not shown.
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 38 KiB |
|
@ -1,8 +1,8 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const testsDir = path.join(__dirname, "tests");
|
||||
const testsDir = path.join(__dirname, 'tests');
|
||||
module.exports = fs.readdirSync(testsDir).map(name => ({
|
||||
name,
|
||||
content: fs.readFileSync(path.join(testsDir, name), "utf8"),
|
||||
}));
|
||||
content: fs.readFileSync(path.join(testsDir, name), 'utf8'),
|
||||
})).sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[package]
|
||||
name = "hyperbuild-fuzz-target"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
authors = ["Wilson Lin <code@wilsonl.in>"]
|
||||
edition = "2018"
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// Implement debug to allow .unwrap().
|
||||
#[derive(Debug)]
|
||||
pub enum ErrorType {
|
||||
EntityFollowingMalformedEntity,
|
||||
NoSpaceBeforeAttr,
|
||||
|
|
Loading…
Reference in New Issue