WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 1483f9e

Browse files
committed
✨ add gif implementation benchmark
1 parent 437d800 commit 1483f9e

File tree

5 files changed

+555
-59
lines changed

5 files changed

+555
-59
lines changed

packages/gif-creator/README.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,40 @@
22

33
Generate a gif file from the grid and snake path.
44

5-
Relies on graphics magic and gifsicle binaries.
5+
# Implementation
6+
7+
It uses `gif-encoder-2` to create a gif and optimize it with `gifslice`.
8+
9+
**Transparency**
10+
11+
Having a transparent background seems to have a quite negative impact on the file size and duration. It should be avoided by default.
12+
13+
Transparency is currently broken for `gif-encoder-2`
14+
15+
**Interpolaction**
16+
17+
It is possible to interpolate betwen two snake step for a smoother movement. This again has a huge impact on the file size and should be avoided.
18+
19+
# Benchmark
20+
21+
I tested some implementations, slower and/or producing a larger gif file.
22+
23+
Here are the results for a realistic test case with solid background and no smooth steps.
24+
25+
| implementation | size | generation duration |
26+
| ------------------------------------- | --------- | ------------------- |
27+
| draw steps on canvas (no output) | - | 1.5s |
28+
| create png image sequence (no output) | - | 4.8s |
29+
| gifEncoder | 7,051Ko | 4.3s |
30+
| gifEncoder+gifslice | **279Ko** | 5.0s |
31+
| ffmpeg | 385Ko | 7.0s |
32+
| ffmpeg+gifslice | 356Ko | 7.0s |
33+
| graphicMagic | 8,959Ko | 20.3s |
34+
35+
Some benchmark comparing different options
36+
37+
| frameByStep | size | generation duration |
38+
| ----------- | ----- | ------------------- |
39+
| 1 | 279Ko | 5.0s |
40+
| 2 | 465Ko | 12.8s |
41+
| 3 | 584Ko | 19.2s |
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import * as fs from "fs";
2+
3+
const runs: any[] = JSON.parse(
4+
fs
5+
.readFileSync(__dirname + `/__snapshots__/benchmark-result.json`)
6+
.toString(),
7+
);
8+
9+
const mean = (values: number[]) =>
10+
values.reduce((s, x) => s + x, 0) / (values.length || 1);
11+
12+
const toLiteralSize = (s: number) => {
13+
return (
14+
(s / 1_024).toLocaleString(undefined, {
15+
maximumFractionDigits: 0,
16+
}) + "Ko"
17+
);
18+
19+
if (s < 1_024) return s.toFixed(1) + "o";
20+
if (s < 1_024 * 1_024) return (s / 1_024).toFixed(1) + "Ko";
21+
return (s / (1_024 * 1_024)).toFixed(1) + "Mo";
22+
};
23+
24+
console.log("| implementation | size | generation duration |\n|---|---|---|");
25+
26+
for (const implementation of new Set(
27+
runs.map((r) => r.implementation),
28+
) as any) {
29+
const rs = runs.filter(
30+
(r) =>
31+
r.implementation === implementation &&
32+
r.frameByStep === 1 &&
33+
r.colorBackground === "white",
34+
);
35+
36+
console.log(
37+
"|" +
38+
[
39+
implementation,
40+
toLiteralSize(mean(rs.map((r) => r.fileSizeByte))),
41+
(mean(rs.map((r) => r.durationMs)) / 1000).toFixed(1) + "s",
42+
].join("|") +
43+
"|",
44+
);
45+
}
46+
47+
console.log(
48+
"\n\n| colorBackground | size | generation duration |\n|---|---|---|",
49+
);
50+
51+
for (const colorBackground of new Set(
52+
runs.map((r) => r.colorBackground),
53+
) as any) {
54+
const rs = runs.filter(
55+
(r) =>
56+
r.implementation === "gifEncoder+gifslice" &&
57+
r.frameByStep === 1 &&
58+
r.colorBackground === colorBackground,
59+
);
60+
61+
console.log(
62+
"|" +
63+
[
64+
colorBackground,
65+
toLiteralSize(mean(rs.map((r) => r.fileSizeByte))),
66+
(mean(rs.map((r) => r.durationMs)) / 1000).toFixed(1) + "s",
67+
].join("|") +
68+
"|",
69+
);
70+
}
71+
72+
console.log("\n\n| frameByStep | size | generation duration |\n|---|---|---|");
73+
74+
for (const frameByStep of new Set(runs.map((r) => r.frameByStep)) as any) {
75+
const rs = runs.filter(
76+
(r) =>
77+
r.implementation === "gifEncoder+gifslice" &&
78+
r.frameByStep === frameByStep &&
79+
r.colorBackground === "white",
80+
);
81+
82+
console.log(
83+
"|" +
84+
[
85+
frameByStep,
86+
toLiteralSize(mean(rs.map((r) => r.fileSizeByte))),
87+
(mean(rs.map((r) => r.durationMs)) / 1000).toFixed(1) + "s",
88+
].join("|") +
89+
"|",
90+
);
91+
}

packages/gif-creator/__tests__/benchmark.ts

Lines changed: 78 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,21 @@ import * as fs from "fs";
22
import { performance } from "perf_hooks";
33
import { createSnakeFromCells } from "@snk/types/snake";
44
import { realistic as grid } from "@snk/types/__fixtures__/grid";
5-
import { AnimationOptions, createGif } from "..";
5+
import {
6+
type AnimationOptions,
7+
type DrawOptions,
8+
createGif as createGif_gifencore_gifslice,
9+
} from "..";
610
import { getBestRoute } from "@snk/solver/getBestRoute";
711
import { getPathToPose } from "@snk/solver/getPathToPose";
8-
import type { Options as DrawOptions } from "@snk/draw/drawWorld";
12+
import {
13+
canvasDrawNoOutput,
14+
createGif_ffmpeg,
15+
createGif_ffmpeg_gifslice,
16+
createGif_gifEncoder,
17+
createGif_graphicMagic,
18+
createPngImageSequenceNoOutput,
19+
} from "./createGif-variant";
920

1021
let snake = createSnakeFromCells(
1122
Array.from({ length: 4 }, (_, i) => ({ x: i, y: -1 })),
@@ -42,47 +53,72 @@ const animationOptions: AnimationOptions = {
4253
};
4354

4455
(async () => {
45-
for (
46-
let length = 10;
47-
length < chain.length;
48-
length += Math.floor((chain.length - 10) / 3 / 10) * 10
49-
) {
50-
const stats: number[] = [];
56+
const runs: any[] = [];
5157

52-
let buffer: Uint8Array;
53-
const start = Date.now();
54-
const chainL = chain.slice(0, length);
55-
for (let k = 0; k < 10 && (Date.now() - start < 10 * 1000 || k < 2); k++) {
56-
const s = performance.now();
57-
buffer = await createGif(
58-
grid,
59-
null,
60-
chainL,
61-
drawOptions,
62-
animationOptions,
63-
);
64-
stats.push(performance.now() - s);
65-
}
58+
for (const [implementation, createGif] of [
59+
["draw steps on canvas (no output)", canvasDrawNoOutput],
60+
["create png image sequence (no output)", createPngImageSequenceNoOutput],
61+
["gifEncoder", createGif_gifEncoder],
62+
["gifEncoder+gifslice", createGif_gifencore_gifslice],
63+
["ffmpeg", createGif_ffmpeg],
64+
["ffmpeg+gifslice", createGif_ffmpeg_gifslice],
65+
["graphicMagic", createGif_graphicMagic],
66+
] as const)
67+
for (const colorBackground of [
68+
//
69+
"transparent",
70+
"white",
71+
])
72+
for (const frameByStep of [
73+
//
74+
1, 2, 3,
75+
])
76+
for (const maxChainLength of [
77+
//
78+
1_000,
79+
]) {
80+
let buffer: Uint8Array;
81+
const start = Date.now();
82+
const chainL = chain.slice(0, maxChainLength);
83+
const chainLength = chainL.length;
84+
const gridDimension = `${grid.width}x${grid.height}`;
6685

67-
console.log(
68-
[
69-
"---",
70-
`grid dimension: ${grid.width}x${grid.height}`,
71-
`chain length: ${length}`,
72-
`resulting size: ${(buffer!.length / 1024).toFixed(1)}ko`,
73-
`generation duration (mean): ${(
74-
stats.reduce((s, x) => x + s) / stats.length
75-
).toLocaleString(undefined, {
76-
maximumFractionDigits: 0,
77-
})}ms`,
78-
"",
79-
].join("\n"),
80-
stats,
81-
);
86+
drawOptions.colorBackground = colorBackground;
87+
animationOptions.frameByStep = frameByStep;
8288

83-
fs.writeFileSync(
84-
__dirname + `/__snapshots__/benchmark-output-${length}.gif`,
85-
buffer!,
86-
);
87-
}
89+
const filename = `benchmark-output-${implementation}-${chainLength}-${animationOptions.frameByStep}-${colorBackground}.gif`;
90+
91+
for (
92+
let k = 0;
93+
k < 10 && (Date.now() - start < 12_000 || k < 2);
94+
k++
95+
) {
96+
const s = performance.now();
97+
buffer = await createGif(
98+
grid,
99+
null,
100+
chainL,
101+
drawOptions,
102+
animationOptions,
103+
);
104+
runs.push({
105+
durationMs: performance.now() - s,
106+
colorBackground,
107+
implementation,
108+
chainLength,
109+
gridDimension,
110+
frameByStep,
111+
fileSizeByte: buffer.length,
112+
filename,
113+
});
114+
}
115+
116+
if (buffer!.length > 0)
117+
fs.writeFileSync(__dirname + `/__snapshots__/${filename}`, buffer!);
118+
119+
fs.writeFileSync(
120+
__dirname + `/__snapshots__/benchmark-result.json`,
121+
"[\n" + runs.map((r) => JSON.stringify(r)).join(",\n") + "\n]",
122+
);
123+
}
88124
})();

0 commit comments

Comments
 (0)