Complete new bench
This commit is contained in:
parent
21297d053a
commit
38f186f73e
|
@ -47,9 +47,7 @@ jobs:
|
||||||
- name: Build bench
|
- name: Build bench
|
||||||
working-directory: ./bench
|
working-directory: ./bench
|
||||||
run: |
|
run: |
|
||||||
sudo apt install -y build-essential
|
./build
|
||||||
npm install
|
|
||||||
./build.sh
|
|
||||||
|
|
||||||
- name: Set up Backblaze B2 CLI
|
- name: Set up Backblaze B2 CLI
|
||||||
uses: wilsonzlin/setup-b2@v3
|
uses: wilsonzlin/setup-b2@v3
|
||||||
|
@ -58,7 +56,7 @@ jobs:
|
||||||
working-directory: ./bench
|
working-directory: ./bench
|
||||||
run: |
|
run: |
|
||||||
b2 authorize-account ${{ secrets.CICD_CLI_B2_KEY_ID }} ${{ secrets.CICD_CLI_B2_APPLICATION_KEY }}
|
b2 authorize-account ${{ secrets.CICD_CLI_B2_KEY_ID }} ${{ secrets.CICD_CLI_B2_APPLICATION_KEY }}
|
||||||
./bench.sh
|
./run
|
||||||
b2 sync ./results/ b2://${{ secrets.CICD_CLI_B2_BUCKET_NAME }}/minify-html/bench/${{ steps.version.outputs.VERSION }}/js/
|
b2 sync ./graphs/ b2://${{ secrets.CICD_CLI_B2_BUCKET_NAME }}/minify-html/bench/${{ steps.version.outputs.VERSION }}/js/
|
||||||
HTML_ONLY=1 ./bench.sh
|
MHB_HTML_ONLY=1 ./run
|
||||||
b2 sync ./results/ b2://${{ secrets.CICD_CLI_B2_BUCKET_NAME }}/minify-html/bench/${{ steps.version.outputs.VERSION }}/core/
|
b2 sync ./graphs/ b2://${{ secrets.CICD_CLI_B2_BUCKET_NAME }}/minify-html/bench/${{ steps.version.outputs.VERSION }}/core/
|
||||||
|
|
|
@ -19,7 +19,8 @@ A Rust HTML minifier meticulously optimised for speed and effectiveness, with bi
|
||||||
|
|
||||||
Comparison with [html-minfier](https://github.com/kangax/html-minifier) and [minimize](https://github.com/Swaagie/minimize), run on the top web pages. [See the breakdown here.](./bench)
|
Comparison with [html-minfier](https://github.com/kangax/html-minifier) and [minimize](https://github.com/Swaagie/minimize), run on the top web pages. [See the breakdown here.](./bench)
|
||||||
|
|
||||||
<img alt="Chart showing speed and compression of HTML minifiers" src="https://wilsonl.in/minify-html/bench/0.6.0/core/average-combined.png">
|
<img alt="Chart showing speed of HTML minifiers" src="https://wilsonl.in/minify-html/bench/0.6.0/core/average-speeds.png">
|
||||||
|
<img alt="Chart showing compression of HTML minifiers" src="https://wilsonl.in/minify-html/bench/0.6.0/core/average-sizes.png">
|
||||||
|
|
||||||
Need even faster performance? Check the [one](https://github.com/wilsonzlin/minify-html/tree/one) branch.
|
Need even faster performance? Check the [one](https://github.com/wilsonzlin/minify-html/tree/one) branch.
|
||||||
|
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
|
/graphs/
|
||||||
/results/
|
/results/
|
||||||
|
|
218
bench/graph.js
218
bench/graph.js
|
@ -1,17 +1,25 @@
|
||||||
const results = require("./results");
|
const results = require("./results");
|
||||||
const https = require("https");
|
const https = require("https");
|
||||||
|
const path = require("path");
|
||||||
|
const fs = require("fs/promises");
|
||||||
|
|
||||||
const colours = {
|
const GRAPHS_DIR = path.join(__dirname, "graphs");
|
||||||
"minify-html": "#041f60",
|
const SPEEDS_GRAPH = path.join(GRAPHS_DIR, "speeds.png");
|
||||||
"@minify-html/js": "#1f77b4",
|
const SIZES_GRAPH = path.join(GRAPHS_DIR, "sizes.png");
|
||||||
minimize: "#ff7f0e",
|
const AVERAGE_SPEEDS_GRAPH = path.join(GRAPHS_DIR, "average-speeds.png");
|
||||||
"html-minifier": "#2ca02c",
|
const AVERAGE_SIZES_GRAPH = path.join(GRAPHS_DIR, "average-sizes.png");
|
||||||
|
|
||||||
|
const speedColours = {
|
||||||
|
"@minify-html/js": "#2e61bd",
|
||||||
|
"minify-html": "#2e61bd",
|
||||||
|
"minify-html-onepass": "#222",
|
||||||
};
|
};
|
||||||
|
const defaultSpeedColour = "rgb(188, 188, 188)";
|
||||||
|
|
||||||
const COLOUR_SPEED_PRIMARY = "#2e61bd";
|
const sizeColours = {
|
||||||
const COLOUR_SPEED_SECONDARY = "rgb(188, 188, 188)";
|
"minify-html": "#2e61bd",
|
||||||
const COLOUR_SIZE_PRIMARY = "#64acce";
|
};
|
||||||
const COLOUR_SIZE_SECONDARY = "rgb(224, 224, 224)";
|
const defaultSizeColour = "rgb(188, 188, 188)";
|
||||||
|
|
||||||
const breakdownChartOptions = (title) => ({
|
const breakdownChartOptions = (title) => ({
|
||||||
options: {
|
options: {
|
||||||
|
@ -30,20 +38,21 @@ const breakdownChartOptions = (title) => ({
|
||||||
scales: {
|
scales: {
|
||||||
xAxes: [
|
xAxes: [
|
||||||
{
|
{
|
||||||
barPercentage: 0.25,
|
|
||||||
gridLines: {
|
gridLines: {
|
||||||
color: "#e2e2e2",
|
color: "#f2f2f2",
|
||||||
},
|
},
|
||||||
ticks: {
|
ticks: {
|
||||||
fontColor: "#666",
|
callback: "$$$_____REPLACE_WITH_TICK_CALLBACK_____$$$",
|
||||||
|
fontColor: "#999",
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
yAxes: [
|
yAxes: [
|
||||||
{
|
{
|
||||||
|
barPercentage: 0.5,
|
||||||
gridLines: {
|
gridLines: {
|
||||||
color: "#ccc",
|
color: "#aaa",
|
||||||
},
|
},
|
||||||
ticks: {
|
ticks: {
|
||||||
fontColor: "#666",
|
fontColor: "#666",
|
||||||
|
@ -61,10 +70,10 @@ const axisLabel = (fontColor, labelString) => ({
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontStyle: "bold",
|
fontStyle: "bold",
|
||||||
labelString,
|
labelString,
|
||||||
padding: 16,
|
padding: 12,
|
||||||
});
|
});
|
||||||
|
|
||||||
const combinedChartOptions = () => ({
|
const averageChartOptions = (label) => ({
|
||||||
options: {
|
options: {
|
||||||
legend: {
|
legend: {
|
||||||
display: false,
|
display: false,
|
||||||
|
@ -77,45 +86,30 @@ const combinedChartOptions = () => ({
|
||||||
},
|
},
|
||||||
ticks: {
|
ticks: {
|
||||||
fontColor: "#555",
|
fontColor: "#555",
|
||||||
fontSize: 24,
|
fontSize: 16,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
yAxes: [
|
yAxes: [
|
||||||
{
|
{
|
||||||
id: "y1",
|
|
||||||
type: "linear",
|
type: "linear",
|
||||||
scaleLabel: axisLabel(COLOUR_SPEED_PRIMARY, "Performance"),
|
scaleLabel: axisLabel("#222", label),
|
||||||
position: "left",
|
position: "left",
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: "$$$_____REPLACE_WITH_TICK_CALLBACK_____$$$",
|
callback: "$$$_____REPLACE_WITH_TICK_CALLBACK_____$$$",
|
||||||
fontColor: COLOUR_SPEED_PRIMARY,
|
fontColor: "#222",
|
||||||
fontSize: 24,
|
fontSize: 16,
|
||||||
},
|
},
|
||||||
gridLines: {
|
gridLines: {
|
||||||
color: "#eee",
|
color: "#eee",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "y2",
|
|
||||||
type: "linear",
|
|
||||||
scaleLabel: axisLabel(COLOUR_SIZE_PRIMARY, "Average size reduction"),
|
|
||||||
position: "right",
|
|
||||||
ticks: {
|
|
||||||
callback: "$$$_____REPLACE_WITH_TICK_CALLBACK_____$$$",
|
|
||||||
fontColor: COLOUR_SIZE_PRIMARY,
|
|
||||||
fontSize: 24,
|
|
||||||
},
|
|
||||||
gridLines: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderChart = (cfg) =>
|
const renderChart = (cfg, width, height) =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
const req = https.request("https://quickchart.io/chart", {
|
const req = https.request("https://quickchart.io/chart", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@ -141,83 +135,109 @@ const renderChart = (cfg) =>
|
||||||
'"$$$_____REPLACE_WITH_TICK_CALLBACK_____$$$"',
|
'"$$$_____REPLACE_WITH_TICK_CALLBACK_____$$$"',
|
||||||
"function(value) {return Math.round(value * 10000) / 100 + '%';}"
|
"function(value) {return Math.round(value * 10000) / 100 + '%';}"
|
||||||
),
|
),
|
||||||
width: 1333,
|
width,
|
||||||
height: 768,
|
height,
|
||||||
format: "png",
|
format: "png",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const averageSpeeds = results
|
await fs.mkdir(GRAPHS_DIR, { recursive: true });
|
||||||
.getSpeedResults()
|
|
||||||
.getAverageRelativeSpeedPerMinifier("@minify-html/js");
|
|
||||||
const averageSizes = results
|
|
||||||
.getSizeResults()
|
|
||||||
.getAverageRelativeSizePerMinifier();
|
|
||||||
const averageLabels = ["minimize", "html-minifier", "@minify-html/js"];
|
|
||||||
|
|
||||||
results.writeAverageCombinedGraph(
|
const res = results.calculate();
|
||||||
await renderChart({
|
const speedMinifiers = [...res.minifiers].sort(
|
||||||
type: "bar",
|
(a, b) => res.minifierAvgOps[a] - res.minifierAvgOps[b]
|
||||||
data: {
|
);
|
||||||
labels: averageLabels,
|
const sizeMinifiers = ["minimize", "html-minifier", "minify-html"];
|
||||||
datasets: [
|
const inputs = Object.keys(res.inputSizes).sort();
|
||||||
{
|
|
||||||
yAxisID: "y1",
|
await fs.writeFile(
|
||||||
backgroundColor: averageLabels.map((n) =>
|
AVERAGE_SPEEDS_GRAPH,
|
||||||
n === "@minify-html/js"
|
await renderChart(
|
||||||
? COLOUR_SPEED_PRIMARY
|
{
|
||||||
: COLOUR_SPEED_SECONDARY
|
type: "bar",
|
||||||
),
|
data: {
|
||||||
data: averageLabels.map((n) => averageSpeeds.get(n)),
|
labels: speedMinifiers.map(m => m.replace(" (", "\n(")),
|
||||||
},
|
datasets: [
|
||||||
{
|
{
|
||||||
yAxisID: "y2",
|
backgroundColor: speedMinifiers.map(
|
||||||
backgroundColor: averageLabels.map((n) =>
|
(n) => speedColours[n] ?? defaultSpeedColour
|
||||||
n === "@minify-html/js"
|
),
|
||||||
? COLOUR_SIZE_PRIMARY
|
data: speedMinifiers.map(
|
||||||
: COLOUR_SIZE_SECONDARY
|
(m) => res.minifierAvgOps[m] / res.maxMinifierAvgOps
|
||||||
),
|
),
|
||||||
data: averageLabels.map((n) => 1 - averageSizes.get(n)),
|
},
|
||||||
},
|
],
|
||||||
],
|
},
|
||||||
|
...averageChartOptions("Performance"),
|
||||||
},
|
},
|
||||||
...combinedChartOptions(),
|
1024,
|
||||||
})
|
768
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const speeds = results
|
await fs.writeFile(
|
||||||
.getSpeedResults()
|
AVERAGE_SIZES_GRAPH,
|
||||||
.getRelativeFileSpeedsPerMinifier("@minify-html/js");
|
await renderChart(
|
||||||
results.writeSpeedsGraph(
|
{
|
||||||
await renderChart({
|
type: "bar",
|
||||||
type: "bar",
|
data: {
|
||||||
data: {
|
labels: sizeMinifiers.map(m => m.replace(" (", "\n(")),
|
||||||
labels: speeds[0][1].map(([n]) => n),
|
datasets: [
|
||||||
datasets: speeds.map(([minifier, fileSpeeds]) => ({
|
{
|
||||||
label: minifier,
|
backgroundColor: sizeMinifiers.map(
|
||||||
backgroundColor: colours[minifier],
|
(n) => sizeColours[n] ?? defaultSizeColour
|
||||||
data: fileSpeeds.map(([_, speed]) => speed),
|
),
|
||||||
})),
|
data: sizeMinifiers.map((m) => res.minifierAvgReduction[m]),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
...averageChartOptions("Reduction"),
|
||||||
},
|
},
|
||||||
...breakdownChartOptions("Operations per second (higher is better)"),
|
1024,
|
||||||
})
|
768
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const sizes = results.getSizeResults().getRelativeFileSizesPerMinifier();
|
await fs.writeFile(
|
||||||
results.writeSizesGraph(
|
SPEEDS_GRAPH,
|
||||||
await renderChart({
|
await renderChart(
|
||||||
type: "bar",
|
{
|
||||||
data: {
|
type: "horizontalBar",
|
||||||
labels: sizes[0][1].map(([n]) => n),
|
data: {
|
||||||
datasets: sizes.map(([minifier, fileSizes]) => ({
|
labels: inputs,
|
||||||
label: minifier,
|
datasets: speedMinifiers.map((minifier) => ({
|
||||||
backgroundColor: colours[minifier],
|
label: minifier,
|
||||||
data: fileSizes.map(([_, size]) => size),
|
data: inputs.map(
|
||||||
})),
|
(input) =>
|
||||||
|
res.perInputOps[minifier][input] / res.maxInputOps[input]
|
||||||
|
),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
...breakdownChartOptions("Operations per second (higher is better)"),
|
||||||
},
|
},
|
||||||
...breakdownChartOptions("Minified size (lower is better)"),
|
900,
|
||||||
})
|
1000
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await fs.writeFile(
|
||||||
|
SIZES_GRAPH,
|
||||||
|
await renderChart(
|
||||||
|
{
|
||||||
|
type: "horizontalBar",
|
||||||
|
data: {
|
||||||
|
labels: inputs,
|
||||||
|
datasets: sizeMinifiers.map((minifier) => ({
|
||||||
|
label: minifier,
|
||||||
|
data: inputs.map((input) => res.perInputReduction[minifier][input]),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
...breakdownChartOptions("Size reduction (higher is better)"),
|
||||||
|
},
|
||||||
|
900,
|
||||||
|
1000
|
||||||
|
)
|
||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
|
|
150
bench/results.js
150
bench/results.js
|
@ -1,99 +1,67 @@
|
||||||
const minifiers = require("./minifiers");
|
const fs = require("fs");
|
||||||
const tests = require("./tests");
|
const path = require("path");
|
||||||
const { join } = require("path");
|
|
||||||
const { mkdirSync, readFileSync, writeFileSync } = require("fs");
|
|
||||||
|
|
||||||
const RESULTS_DIR = join(__dirname, "results");
|
const RESULTS_DIR = path.join(__dirname, "results");
|
||||||
const SPEEDS_JSON = join(RESULTS_DIR, "speeds.json");
|
const INPUTS_DIR = path.join(__dirname, "inputs");
|
||||||
const SPEEDS_GRAPH = join(RESULTS_DIR, "speeds.png");
|
|
||||||
const AVERAGE_COMBINED_GRAPH = join(RESULTS_DIR, "average-combined.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);
|
|
||||||
|
|
||||||
mkdirSync(RESULTS_DIR, { recursive: true });
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
writeSpeedResults(speeds) {
|
calculate: () => {
|
||||||
writeFileSync(SPEEDS_JSON, JSON.stringify(speeds, null, 2));
|
// minifier => avg(ops).
|
||||||
},
|
const minifierAvgOps = {};
|
||||||
writeSizeResults(sizes) {
|
// minifier => avg(1 - output / original).
|
||||||
writeFileSync(SIZES_JSON, JSON.stringify(sizes, null, 2));
|
const minifierAvgReduction = {};
|
||||||
},
|
let maxMinifierAvgOps = 0;
|
||||||
writeAverageCombinedGraph(data) {
|
// minifier => input => ops.
|
||||||
writeFileSync(AVERAGE_COMBINED_GRAPH, data);
|
const perInputOps = {};
|
||||||
},
|
// minifier => input => (1 - output / original).
|
||||||
writeAverageSpeedsGraph(data) {
|
const perInputReduction = {};
|
||||||
writeFileSync(AVERAGE_SPEEDS_GRAPH, data);
|
// input => max(ops).
|
||||||
},
|
const maxInputOps = {};
|
||||||
writeSpeedsGraph(data) {
|
const inputSizes = Object.fromEntries(
|
||||||
writeFileSync(SPEEDS_GRAPH, data);
|
fs.readdirSync(INPUTS_DIR).map((f) => {
|
||||||
},
|
const name = path.basename(f, ".json");
|
||||||
writeAverageSizesGraph(data) {
|
const stats = fs.statSync(path.join(INPUTS_DIR, f));
|
||||||
writeFileSync(AVERAGE_SIZES_GRAPH, data);
|
return [name, stats.size];
|
||||||
},
|
})
|
||||||
writeSizesGraph(data) {
|
);
|
||||||
writeFileSync(SIZES_GRAPH, data);
|
|
||||||
},
|
for (const f of fs.readdirSync(RESULTS_DIR)) {
|
||||||
getSpeedResults() {
|
const minifier = decodeURIComponent(path.basename(f, ".json"));
|
||||||
const data = JSON.parse(readFileSync(SPEEDS_JSON, "utf8"));
|
const data = JSON.parse(
|
||||||
|
fs.readFileSync(path.join(RESULTS_DIR, f), "utf8")
|
||||||
|
);
|
||||||
|
for (const [input, size, iterations, seconds] of data) {
|
||||||
|
const originalSize = inputSizes[input];
|
||||||
|
const ops = 1 / (seconds / iterations);
|
||||||
|
const reduction = 1 - size / originalSize;
|
||||||
|
(minifierAvgOps[minifier] ??= []).push(ops);
|
||||||
|
(minifierAvgReduction[minifier] ??= []).push(reduction);
|
||||||
|
(perInputOps[minifier] ??= {})[input] = ops;
|
||||||
|
(perInputReduction[minifier] ??= {})[input] = reduction;
|
||||||
|
maxInputOps[input] = Math.max(maxInputOps[input] ?? 0, ops);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const minifiers = Object.keys(minifierAvgOps);
|
||||||
|
for (const m of minifiers) {
|
||||||
|
minifierAvgOps[m] =
|
||||||
|
minifierAvgOps[m].reduce((sum, ops) => sum + ops, 0) /
|
||||||
|
minifierAvgOps[m].length;
|
||||||
|
maxMinifierAvgOps = Math.max(maxMinifierAvgOps, minifierAvgOps[m]);
|
||||||
|
minifierAvgReduction[m] =
|
||||||
|
minifierAvgReduction[m].reduce((sum, ops) => sum + ops, 0) /
|
||||||
|
minifierAvgReduction[m].length;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Get minifier-speed pairs.
|
minifierAvgReduction,
|
||||||
getAverageRelativeSpeedPerMinifier(baselineMinifier) {
|
minifierAvgOps,
|
||||||
return new Map(
|
maxMinifierAvgOps,
|
||||||
minifierNames.map((minifier) => [
|
perInputOps,
|
||||||
minifier,
|
perInputReduction,
|
||||||
testNames
|
maxInputOps,
|
||||||
// Get operations per second for each test.
|
inputSizes,
|
||||||
.map(
|
minifiers,
|
||||||
(test) => data[test][minifier] / data[test][baselineMinifier]
|
|
||||||
)
|
|
||||||
// Sum all test operations per second.
|
|
||||||
.reduce((sum, c) => sum + c) /
|
|
||||||
// Divide by tests count to get average operations per second.
|
|
||||||
testNames.length,
|
|
||||||
])
|
|
||||||
);
|
|
||||||
},
|
|
||||||
// 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.
|
|
||||||
getAverageRelativeSizePerMinifier() {
|
|
||||||
return new Map(
|
|
||||||
minifierNames.map((minifier) => [
|
|
||||||
minifier,
|
|
||||||
testNames
|
|
||||||
.map((test) => data[test][minifier].relative)
|
|
||||||
.reduce((sum, c) => sum + c) / testNames.length,
|
|
||||||
])
|
|
||||||
);
|
|
||||||
},
|
|
||||||
// Get minifier-sizes pairs.
|
|
||||||
getRelativeFileSizesPerMinifier() {
|
|
||||||
return minifierNames.map((minifier) => [
|
|
||||||
minifier,
|
|
||||||
testNames.map((test) => [test, data[test][minifier].relative]),
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,10 +14,11 @@ const minifyHtmlCfg = minifyHtml.createConfiguration({
|
||||||
const results = fs.readdirSync(inputDir).map((name) => {
|
const results = fs.readdirSync(inputDir).map((name) => {
|
||||||
const src = fs.readFileSync(path.join(inputDir, name));
|
const src = fs.readFileSync(path.join(inputDir, name));
|
||||||
const start = process.hrtime.bigint();
|
const start = process.hrtime.bigint();
|
||||||
|
let len;
|
||||||
for (let i = 0; i < iterations; i++) {
|
for (let i = 0; i < iterations; i++) {
|
||||||
minifyHtml.minify(src, minifyHtmlCfg);
|
len = minifyHtml.minify(src, minifyHtmlCfg).byteLength;
|
||||||
}
|
}
|
||||||
const elapsed = process.hrtime.bigint() - start;
|
const elapsed = process.hrtime.bigint() - start;
|
||||||
return [name, Number(elapsed) / 1_000_000_000];
|
return [name, len, iterations, Number(elapsed) / 1_000_000_000];
|
||||||
});
|
});
|
||||||
console.log(JSON.stringify(results));
|
console.log(JSON.stringify(results));
|
|
@ -5,5 +5,5 @@
|
||||||
- `MHB_ITERATIONS`: times to run each input.
|
- `MHB_ITERATIONS`: times to run each input.
|
||||||
- `MHB_INPUT_DIR`: path to directory containing inputs. Files should be read from this directory and used as the inputs.
|
- `MHB_INPUT_DIR`: path to directory containing inputs. Files should be read from this directory and used as the inputs.
|
||||||
- `MHB_HTML_ONLY`: if set to `1`, `minify_css` and `minify_js` should be disabled.
|
- `MHB_HTML_ONLY`: if set to `1`, `minify_css` and `minify_js` should be disabled.
|
||||||
- The output should be a JSON array of pairs, where each pair represents the input name and execution time in seconds (as a floating point value).
|
- The output should be a JSON array of tuples, where each tuples contains the input name, output size, iterations, and execution time in seconds (as a floating point value).
|
||||||
- The execution time should be measured using high-precision monotonic system clocks where possible.
|
- The execution time should be measured using high-precision monotonic system clocks where possible.
|
||||||
|
|
|
@ -48,10 +48,11 @@ const htmlMinifierCfg = {
|
||||||
const results = fs.readdirSync(inputDir).map((name) => {
|
const results = fs.readdirSync(inputDir).map((name) => {
|
||||||
const src = fs.readFileSync(path.join(inputDir, name), "utf8");
|
const src = fs.readFileSync(path.join(inputDir, name), "utf8");
|
||||||
const start = process.hrtime.bigint();
|
const start = process.hrtime.bigint();
|
||||||
|
let len;
|
||||||
for (let i = 0; i < iterations; i++) {
|
for (let i = 0; i < iterations; i++) {
|
||||||
htmlMinifier.minify(src, htmlMinifierCfg);
|
len = htmlMinifier.minify(src, htmlMinifierCfg).length;
|
||||||
}
|
}
|
||||||
const elapsed = process.hrtime.bigint() - start;
|
const elapsed = process.hrtime.bigint() - start;
|
||||||
return [name, Number(elapsed) / 1_000_000_000];
|
return [name, len, iterations, Number(elapsed) / 1_000_000_000];
|
||||||
});
|
});
|
||||||
console.log(JSON.stringify(results));
|
console.log(JSON.stringify(results));
|
||||||
|
|
|
@ -11,7 +11,7 @@ fn main() {
|
||||||
|
|
||||||
let tests = fs::read_dir(input_dir).unwrap().map(|d| d.unwrap());
|
let tests = fs::read_dir(input_dir).unwrap().map(|d| d.unwrap());
|
||||||
|
|
||||||
let mut results: Vec<(String, f64)> = Vec::new();
|
let mut results: Vec<(String, usize, usize, f64)> = Vec::new();
|
||||||
let cfg = Cfg {
|
let cfg = Cfg {
|
||||||
minify_css: !html_only,
|
minify_css: !html_only,
|
||||||
minify_js: !html_only,
|
minify_js: !html_only,
|
||||||
|
@ -20,12 +20,13 @@ fn main() {
|
||||||
for t in tests {
|
for t in tests {
|
||||||
let source = fs::read(t.path()).unwrap();
|
let source = fs::read(t.path()).unwrap();
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
let mut len = 0;
|
||||||
for _ in 0..iterations {
|
for _ in 0..iterations {
|
||||||
let mut data = source.to_vec();
|
let mut data = source.to_vec();
|
||||||
let _ = in_place(&mut data, &cfg).expect("failed to minify");
|
len = in_place(&mut data, &cfg).expect("failed to minify");
|
||||||
};
|
};
|
||||||
let elapsed = start.elapsed().as_secs_f64();
|
let elapsed = start.elapsed().as_secs_f64();
|
||||||
results.push((t.file_name().into_string().unwrap(), elapsed));
|
results.push((t.file_name().into_string().unwrap(), len, iterations, elapsed));
|
||||||
};
|
};
|
||||||
|
|
||||||
serde_json::to_writer(stdout(), &results).unwrap();
|
serde_json::to_writer(stdout(), &results).unwrap();
|
|
@ -11,7 +11,7 @@ fn main() {
|
||||||
|
|
||||||
let tests = fs::read_dir(input_dir).unwrap().map(|d| d.unwrap());
|
let tests = fs::read_dir(input_dir).unwrap().map(|d| d.unwrap());
|
||||||
|
|
||||||
let mut results: Vec<(String, f64)> = Vec::new();
|
let mut results: Vec<(String, usize, usize, f64)> = Vec::new();
|
||||||
let mut cfg = Cfg::new();
|
let mut cfg = Cfg::new();
|
||||||
if !html_only {
|
if !html_only {
|
||||||
cfg.minify_css = true;
|
cfg.minify_css = true;
|
||||||
|
@ -21,11 +21,12 @@ fn main() {
|
||||||
for t in tests {
|
for t in tests {
|
||||||
let source = fs::read(t.path()).unwrap();
|
let source = fs::read(t.path()).unwrap();
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
let mut len = 0;
|
||||||
for _ in 0..iterations {
|
for _ in 0..iterations {
|
||||||
let _ = minify(&source, &cfg);
|
len = minify(&source, &cfg).len();
|
||||||
};
|
};
|
||||||
let elapsed = start.elapsed().as_secs_f64();
|
let elapsed = start.elapsed().as_secs_f64();
|
||||||
results.push((t.file_name().into_string().unwrap(), elapsed));
|
results.push((t.file_name().into_string().unwrap(), len, iterations, elapsed));
|
||||||
};
|
};
|
||||||
|
|
||||||
serde_json::to_writer(stdout(), &results).unwrap();
|
serde_json::to_writer(stdout(), &results).unwrap();
|
|
@ -61,10 +61,11 @@ const plugins = htmlOnly ? [] : [jsCssPlugin];
|
||||||
const results = fs.readdirSync(inputDir).map((name) => {
|
const results = fs.readdirSync(inputDir).map((name) => {
|
||||||
const src = fs.readFileSync(path.join(inputDir, name), "utf8");
|
const src = fs.readFileSync(path.join(inputDir, name), "utf8");
|
||||||
const start = process.hrtime.bigint();
|
const start = process.hrtime.bigint();
|
||||||
|
let len;
|
||||||
for (let i = 0; i < iterations; i++) {
|
for (let i = 0; i < iterations; i++) {
|
||||||
new minimize({ plugins }).parse(src);
|
len = new minimize({ plugins }).parse(src).length;
|
||||||
}
|
}
|
||||||
const elapsed = process.hrtime.bigint() - start;
|
const elapsed = process.hrtime.bigint() - start;
|
||||||
return [name, Number(elapsed) / 1_000_000_000];
|
return [name, len, iterations, Number(elapsed) / 1_000_000_000];
|
||||||
});
|
});
|
||||||
console.log(JSON.stringify(results));
|
console.log(JSON.stringify(results));
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
const fs = require("fs");
|
|
||||||
const path = require("path");
|
|
||||||
const minifiers = require("./minifiers");
|
|
||||||
const results = require("./results");
|
|
||||||
const tests = require("./tests");
|
|
||||||
|
|
||||||
const sizes = {};
|
|
||||||
const setSize = (program, test, result) => {
|
|
||||||
if (!sizes[test]) {
|
|
||||||
sizes[test] = {
|
|
||||||
original: {
|
|
||||||
absolute: tests.find((t) => t.name === test).contentAsString.length,
|
|
||||||
relative: 1,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const original = sizes[test].original.absolute;
|
|
||||||
sizes[test][program] = {
|
|
||||||
absolute: result,
|
|
||||||
relative: result / original,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
for (const t of tests) {
|
|
||||||
for (const m of Object.keys(minifiers)) {
|
|
||||||
try {
|
|
||||||
const min = await minifiers[m](t.contentAsString, t.contentAsBuffer);
|
|
||||||
// If `min` is a Buffer, convert to string (interpret as UTF-8) to get canonical length.
|
|
||||||
setSize(m, t.name, min.toString().length);
|
|
||||||
const minPath = path.join(__dirname, "min", m, `${t.name}.html`);
|
|
||||||
fs.mkdirSync(path.dirname(minPath), { recursive: true });
|
|
||||||
fs.writeFileSync(minPath, min);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`Failed to run ${m} on test ${t.name}:`);
|
|
||||||
console.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
results.writeSizeResults(sizes);
|
|
||||||
})();
|
|
|
@ -1,98 +0,0 @@
|
||||||
const benchmark = require("benchmark");
|
|
||||||
const childProcess = require("child_process");
|
|
||||||
const minimist = require("minimist");
|
|
||||||
const path = require("path");
|
|
||||||
const minifiers = require("./minifiers");
|
|
||||||
const results = require("./results");
|
|
||||||
const tests = require("./tests");
|
|
||||||
|
|
||||||
const args = minimist(process.argv.slice(2));
|
|
||||||
const shouldRunRust = !!args.rust;
|
|
||||||
|
|
||||||
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 runTest = (test) =>
|
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
// Run JS libraries.
|
|
||||||
const suite = new benchmark.Suite();
|
|
||||||
for (const m of Object.keys(minifiers)) {
|
|
||||||
suite.add(m, {
|
|
||||||
defer: true,
|
|
||||||
fn(deferred) {
|
|
||||||
Promise.resolve(
|
|
||||||
minifiers[m](test.contentAsString, test.contentAsBuffer)
|
|
||||||
).then(() => deferred.resolve());
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
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 });
|
|
||||||
});
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
const speeds = fromEntries(tests.map((t) => [t.name, {}]));
|
|
||||||
|
|
||||||
// Run Rust library.
|
|
||||||
if (shouldRunRust) {
|
|
||||||
for (const [testName, testOps] of JSON.parse(
|
|
||||||
cmd(
|
|
||||||
path.join(
|
|
||||||
__dirname,
|
|
||||||
"minify-html-bench",
|
|
||||||
"target",
|
|
||||||
"release",
|
|
||||||
"minify-html-bench"
|
|
||||||
),
|
|
||||||
"--iterations",
|
|
||||||
512,
|
|
||||||
"--tests",
|
|
||||||
path.join(__dirname, "tests")
|
|
||||||
)
|
|
||||||
)) {
|
|
||||||
Object.assign(speeds[testName], { ["minify-html"]: testOps });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const t of tests) {
|
|
||||||
Object.assign(speeds[t.name], await runTest(t));
|
|
||||||
}
|
|
||||||
results.writeSpeedResults(speeds);
|
|
||||||
})();
|
|
Loading…
Reference in New Issue