🚀 benchmark

This commit is contained in:
platane
2020-07-21 18:15:41 +02:00
parent 1898ec16e4
commit e637604df1
13 changed files with 182 additions and 61 deletions

View File

@@ -20,6 +20,19 @@ jobs:
- run: yarn test --ci
- run: yarn build:action
test-benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1.4.2
with:
node-version: 14
- uses: bahmutov/npm-install@v1.4.1
- run: ( cd packages/compute ; yarn benchmark )
test-action:
runs-on: ubuntu-latest

View File

@@ -1,6 +1,5 @@
import { getGithubUserContribution, Cell } from "@snk/github-user-contribution";
import { generateEmptyGrid } from "@snk/compute/generateGrid";
import { setColor } from "@snk/compute/grid";
import { setColor, createEmptyGrid } from "@snk/compute/grid";
import { computeBestRun } from "@snk/compute";
import { createGif } from "../gif-creator";
@@ -8,7 +7,7 @@ export const userContributionToGrid = (cells: Cell[]) => {
const width = Math.max(0, ...cells.map((c) => c.x)) + 1;
const height = Math.max(0, ...cells.map((c) => c.y)) + 1;
const grid = generateEmptyGrid(width, height);
const grid = createEmptyGrid(width, height);
for (const c of cells) setColor(grid, c.x, c.y, c.k === 0 ? null : c.k);
return grid;

View File

@@ -0,0 +1,75 @@
// @ts-ignore
import * as ParkMiller from "park-miller";
import { generateRandomGrid } from "../generateGrid";
import { Snake } from "../snake";
import { Grid } from "../grid";
import { computeBestRun } from "..";
import { performance } from "perf_hooks";
const snake0 = [
{ x: 4, y: -1 },
{ x: 3, y: -1 },
{ x: 2, y: -1 },
{ x: 1, y: -1 },
{ x: 0, y: -1 },
];
const gameOptions = {
maxSnakeLength: 5,
colors: [1, 2, 3, 4],
};
const run = (grid: Grid, snake0: Snake, k: number) => {
const stats: number[] = [];
const s0 = performance.now();
const M = 60 * 1000;
let n = 40;
while (performance.now() - s0 < M && n-- > 0) {
const s = performance.now();
computeBestRun(grid, snake0, gameOptions, k);
stats.push(performance.now() - s);
}
return stats;
};
const report = (arr: number[]) => {
const average = (arr: number[]) =>
arr.reduce((s, x) => s + x, 0) / arr.length;
const spread = (arr: number[]) => {
const m = average(arr);
const v = average(arr.map((x) => Math.pow(x - m, 2)));
return Math.sqrt(v) / m;
};
const format = (x: number): string => {
const u = Math.floor(x / 1000);
const d = Math.floor(x % 1000).toString();
return u === 0 ? d : format(u) + " " + d.padEnd(3, "0");
};
return `${format(average(arr)).padStart(12)} ms ±${(spread(arr) * 100)
.toFixed(2)
.padStart(5)}% (x${arr.length.toString().padStart(3)})`;
};
[
//
[10, 10, 1000],
[21, 7, 1000],
[42, 7, 1000],
[42, 7, 5000],
[42, 7, 14000],
[42, 7, 30000],
].forEach(([w, h, k]) => {
const random = new ParkMiller(1);
const grid = generateRandomGrid(w, h, { ...gameOptions, emptyP: 3 }, (a, b) =>
random.integerInRange(a, b)
);
const stats = run(grid, snake0, k);
console.log(`${w}x${h} : ${k}\n ${report(stats)}\n`);
});

View File

@@ -1,8 +1,7 @@
import { generateEmptyGrid } from "../generateGrid";
import { setColor, getColor, isInside } from "../grid";
import { createEmptyGrid, setColor, getColor, isInside } from "../grid";
it("should set / get cell", () => {
const grid = generateEmptyGrid(2, 3);
const grid = createEmptyGrid(2, 3);
expect(getColor(grid, 0, 1)).toBe(null);
@@ -20,7 +19,7 @@ test.each([
[2, 1, false],
[0, 3, false],
])("isInside", (x, y, output) => {
const grid = generateEmptyGrid(2, 3);
const grid = createEmptyGrid(2, 3);
expect(isInside(grid, x, y)).toBe(output);
});

View File

@@ -1,4 +1,4 @@
import { snakeSelfCollide } from "../snake";
import { snakeSelfCollide, snakeWillSelfCollide } from "../snake";
test.each([
[[{ x: 0, y: 0 }], false],
@@ -22,3 +22,41 @@ test.each([
])("should report snake collision", (snake, collide) => {
expect(snakeSelfCollide(snake)).toBe(collide);
});
test.each([
[
[
{ x: 1, y: 7 },
{ x: 0, y: 7 },
{ x: 0, y: 8 },
{ x: 0, y: 9 },
{ x: 1, y: 9 },
],
{ x: 1, y: 7 },
true,
],
[
[
{ x: 1, y: 7 },
{ x: 0, y: 7 },
{ x: 0, y: 8 },
{ x: 0, y: 9 },
{ x: 1, y: 9 },
],
{ x: 1, y: 8 },
false,
],
[
[
{ x: 1, y: 7 },
{ x: 0, y: 7 },
{ x: 0, y: 8 },
{ x: 0, y: 9 },
{ x: 1, y: 9 },
],
{ x: 1, y: 8 },
false,
],
])("should report snake collision next", (snake, { x, y }, collide) => {
expect(snakeWillSelfCollide(snake, x, y)).toBe(collide);
});

View File

@@ -1,10 +1,9 @@
import { step } from "../step";
import { generateEmptyGrid } from "../generateGrid";
import { around4 } from "../point";
import { setColor, getColor } from "../grid";
import { createEmptyGrid, setColor, getColor } from "../grid";
it("should move snake", () => {
const grid = generateEmptyGrid(4, 3);
const grid = createEmptyGrid(4, 3);
const snake = [{ x: 1, y: 1 }];
const direction = around4[0];
const stack: number[] = [];
@@ -36,7 +35,7 @@ it("should move snake", () => {
});
it("should move short snake", () => {
const grid = generateEmptyGrid(8, 3);
const grid = createEmptyGrid(8, 3);
const snake = [{ x: 1, y: 1 }];
const direction = around4[0];
const stack: number[] = [];
@@ -75,7 +74,7 @@ it("should move short snake", () => {
});
it("should pick up fruit", () => {
const grid = generateEmptyGrid(4, 3);
const grid = createEmptyGrid(4, 3);
const snake = [{ x: 1, y: 1 }];
const direction = around4[0];
const stack: number[] = [];

View File

@@ -1,9 +1,7 @@
import { Grid, Color } from "./grid";
import { Grid, Color, setColor, createEmptyGrid } from "./grid";
const rand = (a: number, b: number) => Math.floor(Math.random() * (b - a)) + a;
export const generateEmptyGrid = (width: number, height: number) =>
generateRandomGrid(width, height, { colors: [], emptyP: 1 });
const defaultRand = (a: number, b: number) =>
Math.floor(Math.random() * (b - a)) + a;
export const generateRandomGrid = (
width: number,
@@ -11,17 +9,17 @@ export const generateRandomGrid = (
options: { colors: Color[]; emptyP: number } = {
colors: [1, 2, 3],
emptyP: 2,
}
},
rand = defaultRand
): Grid => {
const g = {
width,
height,
data: Array.from({ length: width * height }, () => {
const x = rand(-options.emptyP, options.colors.length);
const grid = createEmptyGrid(width, height);
return x < 0 ? null : options.colors[x];
}),
};
for (let x = width; x--; )
for (let y = height; y--; ) {
const k = rand(-options.emptyP, options.colors.length);
return g;
if (k >= 0) setColor(grid, x, y, options.colors[k]);
}
return grid;
};

View File

@@ -28,3 +28,9 @@ export const setColor = (
) => {
grid.data[getIndex(grid, x, y)] = color;
};
export const createEmptyGrid = (width: number, height: number) => ({
width,
height,
data: Array.from({ length: width * height }, () => null),
});

View File

@@ -80,12 +80,9 @@ const unwrap = (c: ReturnType<typeof createCell> | null): Point[] =>
export const computeBestRun = (
grid0: Grid,
snake0: Snake,
options: { maxSnakeLength: number; colors: Color[] }
options: { maxSnakeLength: number; colors: Color[] },
u = 8000
) => {
// const grid = copyGrid(grid0);
// const snake = copySnake(snake0);
// const stack: Color[] = [];
const computeHeuristic = createComputeHeuristic(
grid0,
snake0,
@@ -104,8 +101,6 @@ export const computeBestRun = (
),
];
let u = 8000;
let best = openList[0];
while (openList.length && u-- > 0) {
@@ -147,28 +142,4 @@ export const computeBestRun = (
}
return unwrap(best);
// while (!isGridEmpty(g) && u-- > 0) {
// let direction;
// for (let k = 10; k--; ) {
// direction = around4[Math.floor(Math.random() * around4.length)];
// const sn = copySnake(s);
// stepSnake(sn, direction, options);
// if (isInsideLarge(g, 1, sn[0].x, sn[0].y) && !snakeSelfCollide(sn)) {
// break;
// } else {
// direction = undefined;
// }
// }
// if (direction !== undefined) {
// step(g, s, q, direction, options);
// commands.push(direction);
// }
// }
// return commands;
};

View File

@@ -1,4 +1,11 @@
{
"name": "@snk/compute",
"version": "1.0.0"
"version": "1.0.0",
"devDependencies": {
"@zeit/ncc": "0.22.3",
"park-miller": "1.1.0"
},
"scripts": {
"benchmark": "ncc run __tests__/benchmark.ts --quiet"
}
}

View File

@@ -16,6 +16,17 @@ export const snakeSelfCollideNext = (
return false;
};
export const snakeWillSelfCollide = (
snake: Snake,
headx: number,
heady: number
) => {
for (let i = 0; i < snake.length - 1; i++)
if (snake[i].x === headx && snake[i].y === heady) return true;
return false;
};
export const snakeSelfCollide = (snake: Snake) => {
for (let i = 1; i < snake.length; i++)
if (snake[i].x === snake[0].x && snake[i].y === snake[0].y) return true;

View File

@@ -1,7 +1,7 @@
import { Grid, Color, getColor, isInside, setColor } from "./grid";
import { Point } from "./point";
const moveSnake = (snake: Point[], headx: number, heady: number) => {
export const moveSnake = (snake: Point[], headx: number, heady: number) => {
for (let k = snake.length - 1; k > 0; k--) {
snake[k].x = snake[k - 1].x;
snake[k].y = snake[k - 1].y;

View File

@@ -4896,6 +4896,11 @@ param-case@^3.0.3:
dot-case "^3.0.3"
tslib "^1.10.0"
park-miller@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/park-miller/-/park-miller-1.1.0.tgz#5831ca3b353166735e52a9c5fbb9ff8b1211b7c5"
integrity sha512-6mLXc2jkM9dcavPoxDHfof2QM/baCsrgK51iHbJDHl94AwymJv2Z/iGKQVwJMWUAXFiIV6FzY9UskjU3+KvLuA==
parse-asn1@^5.0.0, parse-asn1@^5.1.5:
version "5.1.5"
resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e"