From 13ce69fd81bf3b28271993629645bef6896d78b4 Mon Sep 17 00:00:00 2001 From: Jamie Bray Date: Sun, 27 Mar 2022 14:00:35 +0800 Subject: [PATCH] Move computations in worker --- readme.md | 35 ++++++++++++++++++++++++++++------- src/benchmark/bench.ts | 8 ++++++++ src/graphics/loop.ts | 7 ++++++- src/logic/board.ts | 14 ++++++++------ src/logic/next.ts | 30 +++++++++++++++++++++++------- src/logic/worker.ts | 8 ++++++++ tsconfig.json | 4 ++-- 7 files changed, 83 insertions(+), 23 deletions(-) create mode 100644 src/logic/worker.ts diff --git a/readme.md b/readme.md index aade0d4..0abce21 100644 --- a/readme.md +++ b/readme.md @@ -36,26 +36,34 @@ http://localhost:8080/benchmark ## Log of benchmark improvements -### Initial benchmark (75s) +### First working benchmark (~75s) -Fairly much a manual transpilation from the original [C++ implementation](https://github.com/Jumbub/game-of-speed) to Typescript, but without using an advanced JS. +Naive and simple manual transpilation of the [C++ implementation](https://github.com/Jumbub/game-of-speed) to Typescript, without any advanced Javascript (typed arrays, workers, etc). [c15cae4d00beb59f5e6e50f495a323eca3f24eb5](https://github.com/Jumbub/game-of-snails/commit/9227c6a55ede200a1b6fe827c93010963e704f3d) -### Use UInt8Array instead of regular Array for booleans (53s) +### Use smaller typed arrays for skip cells (~53s) -First step towards some advanced JS. +First attempt at using typed arrays, using Uint8Arrays to store the skip data. +(matching how the c++ implementation does it) [01c5c850ccef4075d01441983a55cae6aa127c2a](https://github.com/Jumbub/game-of-snails/commit/01c5c850ccef4075d01441983a55cae6aa127c2a) -### Closer towards the C++ code, first "real" benchmark (36.86s) +### Use smaller typed arrays for data cells (~37s) -This benchmark achieves the 30 renders per second requirement, and also adds a check to ensure the average never dips below 29.97. +Use Uint8Array's for storing state of a cell, then create the Uint32Array required for rendering at render time. +(matching how the c++ implementation does it) -The code is also another couple steps closer to the C++ algorithm, because we're now storing our cells in the same data structure, and performing the same render calculations. +This benchmark also adds a requirement that the renders per second never drops below 29.97. [1263580a75a6ac861616411fddc1a9605e995292](https://github.com/Jumbub/game-of-snails/commit/1263580a75a6ac861616411fddc1a9605e995292) +### Moving the computation into a worker (~28s) + +Create a single worker which listens for computation requests from the primary thread. + +[COMMIT](TODO) +
## Interesting findings @@ -69,3 +77,16 @@ The code is also another couple steps closer to the C++ algorithm, because we're ### Casting from String 1s and 0s to Numbers [Benchmark demonstrating ways of casting from an array of string 1s or 0s.](https://perf.link/#eyJpZCI6InUzeXptZW8zZWF1IiwidGl0bGUiOiJGaW5kaW5nIG51bWJlcnMgaW4gYW4gYXJyYXkgb2YgMTAwMCIsImJlZm9yZSI6ImNvbnN0IGRhdGEgPSBbLi4uQXJyYXkoMTAwMDApLmtleXMoKV0ubWFwKGkgPT4gU3RyaW5nKE1hdGgucm91bmQoTWF0aC5yYW5kb20oKSkpKSIsInRlc3RzIjpbeyJuYW1lIjoiRmluZCBpdGVtIDEwMCIsImNvZGUiOiJkYXRhLm1hcCh4ID0%2BIHggPT09ICcxJyA%2FIDEgOiAwKSIsInJ1bnMiOlsyMTY2LDM4MzMsMzgzMyw1MzMzLDU4MzMsNTY2Niw1ODMzLDMxNjYsMzgzMyw0MzMzLDQ1MDAsNTUwMCw1ODMzLDMzMzMsNTMzMyw1NTAwLDU1MDAsNjAwMCw1MDAwLDQxNjYsNjUwMCw2MTY2LDU4MzMsNTMzMyw1MTY2LDYzMzMsNDgzMyw1NTAwLDUwMDAsNTAwMCwzMDAwLDUzMzMsNTY2NiwzNTAwLDM2NjYsNTgzMyw1MzMzLDUwMDAsNDUwMCw0MzMzLDU1MDAsNTY2Niw2NTAwLDI4MzMsNTgzMyw0MTY2LDYxNjYsNjMzMyw1MTY2LDM4MzMsNjUwMCwzMzMzLDM4MzMsNTAwMCwyODMzLDU1MDAsNDE2Niw2MTY2LDU1MDAsNTMzMyw1MTY2LDQwMDAsNDY2Niw1MTY2LDM1MDAsNTUwMCw2MzMzLDM2NjYsNDgzMyw1NjY2LDYwMDAsNDAwMCw0ODMzLDYzMzMsNTUwMCw2MTY2LDE1MDAsNTMzMywxNTAwLDU2NjYsNTAwMCw1MDAwLDUwMDAsNjAwMCw1MzMzLDUwMDAsNTUwMCw2MTY2LDM4MzMsMzY2Niw2NTAwLDY1MDAsNTAwMCw1NTAwLDQ1MDAsNTY2Niw0MDAwLDQ2NjYsNTE2Niw1NjY2XSwib3BzIjo0OTU0fSx7Im5hbWUiOiJGaW5kIGl0ZW0gMjAwIiwiY29kZSI6ImRhdGEubWFwKHggPT4gTnVtYmVyKHgpKSIsInJ1bnMiOlsyODMzLDQwMDAsNTY2Niw3MTY2LDY2NjYsNjMzMyw1ODMzLDYzMzMsNTY2Niw1MzMzLDYwMDAsNDgzMyw2MzMzLDQzMzMsNTY2Niw2MzMzLDU1MDAsNTE2Niw2MDAwLDUxNjYsNjMzMyw1NTAwLDQzMzMsNTY2Niw0MzMzLDQxNjYsNjE2Niw1MzMzLDUwMDAsNTUwMCw1NTAwLDU1MDAsNTAwMCw1NjY2LDY4MzMsNTMzMyw0ODMzLDU4MzMsNzAwMCw2MzMzLDY2NjYsNjMzMyw2MDAwLDUwMDAsNjE2Niw2ODMzLDY2NjYsNjUwMCw1MDAwLDYwMDAsNjAwMCw0MDAwLDU1MDAsNTUwMCw2MzMzLDYxNjYsNzAwMCw3MDAwLDUwMDAsNjAwMCw0ODMzLDYwMDAsNTY2Niw1MTY2LDYxNjYsNzE2Niw2MzMzLDI4MzMsNjMzMyw2MTY2LDc1MDAsMTE2Niw1MTY2LDU2NjYsNjE2Niw2MzMzLDUwMDAsNDE2Niw1MTY2LDY1MDAsNTE2Niw1NTAwLDY2NjYsNTUwMCw3MTY2LDUwMDAsNDY2Niw1NTAwLDYxNjYsNDAwMCw2MTY2LDE1MDAsNjgzMyw2MDAwLDYxNjYsNTgzMyw2MTY2LDYzMzMsNTAwMCw2ODMzXSwib3BzIjo1NjI2fSx7Im5hbWUiOiJGaW5kIGl0ZW0gNDAwIiwiY29kZSI6ImRhdGEubWFwKHggPT4gcGFyc2VJbnQoeCkpIiwicnVucyI6WzIxNjYsNTAwMCw2MzMzLDYwMDAsNjUwMCw1NjY2LDQ4MzMsNTMzMyw0MDAwLDUzMzMsNTUwMCw3MTY2LDU2NjYsNzAwMCwzNTAwLDY2NjYsNjY2Niw2MTY2LDYxNjYsNjUwMCw2MDAwLDUwMDAsNzY2Niw0ODMzLDQ1MDAsNTY2Niw2MzMzLDYwMDAsNTAwMCwzMzMzLDY2NjYsMTUwMCw1ODMzLDY4MzMsNjE2Niw0MzMzLDYxNjYsNjMzMyw3MTY2LDQ2NjYsNzE2Niw1NTAwLDUwMDAsNTMzMyw2NTAwLDcwMDAsNjgzMyw2NTAwLDUzMzMsNTY2Niw1MDAwLDYwMDAsNjMzMyw1MTY2LDUzMzMsNDgzMyw2MzMzLDY2NjYsNTMzMyw2ODMzLDYzMzMsNjgzMyw2NTAwLDQ4MzMsNjE2Niw1MzMzLDY1MDAsNTAwMCw2MDAwLDc1MDAsNzE2Niw0MzMzLDYzMzMsNTUwMCw1MTY2LDQwMDAsNzAwMCw2NTAwLDQ4MzMsNTgzMyw2ODMzLDYxNjYsNTMzMyw2NTAwLDY2NjYsNjAwMCw3MzMzLDQ1MDAsNDMzMyw2ODMzLDY1MDAsNjE2Niw1NTAwLDM4MzMsNTAwMCw3MTY2LDY1MDAsNTgzMyw2NjY2LDYwMDBdLCJvcHMiOjU3ODF9XSwidXBkYXRlZCI6IjIwMjItMDMtMjZUMTA6NTM6MjUuNDI3WiJ9) + +### Having the same work done in a worker is slower + +The speed at this [commit](TODO) went from 35.98 gens/sec to 3.7 seconds _just_ by moving the same work to a worker. + +### SharedArrayBuffer restrictions + +In Chrome, this feature requires the page is loaded with the following headers: + +``` +Cross-Origin-Embedder-Policy: require-corp +Cross-Origin-Opener-Policy: same-origin +``` diff --git a/src/benchmark/bench.ts b/src/benchmark/bench.ts index 8029b48..8803dcf 100644 --- a/src/benchmark/bench.ts +++ b/src/benchmark/bench.ts @@ -20,6 +20,14 @@ generations/second: ${(meta.generations / seconds).toFixed(2)} renders/second: ${rps.toFixed(2)} ${valid}`; + const result = document.createElement('div'); + result.style.position = 'fixed'; + result.style.padding = '10px'; + result.style.backgroundColor = 'black'; + result.style.color = 'white'; + result.innerHTML = report.replace(/\n/g, '
'); + document.body.append(result); + console.log(report); }; diff --git a/src/graphics/loop.ts b/src/graphics/loop.ts index bc47c9a..e296f4f 100644 --- a/src/graphics/loop.ts +++ b/src/graphics/loop.ts @@ -7,6 +7,7 @@ import { render } from './render.js'; export type Meta = { board: Board; context: CanvasRenderingContext2D; + workers: Worker[]; maxGenerations: number; generations: number; renders: number; @@ -23,6 +24,9 @@ export const setup = ( ): Meta => { const board = newBoard(viewWidth, viewHeight); const context = newContext(viewWidth, viewHeight); + const workers = Array(1) + .fill(0) + .map((_, i) => new Worker('/logic/worker.js', { name: 'worker', type: 'module' })); const meta: Meta = { board, context, @@ -31,6 +35,7 @@ export const setup = ( onDone, generations: 0, renders: 0, + workers, }; window.addEventListener('blur', () => { @@ -51,8 +56,8 @@ export const run = (meta: Meta) => { meta.renders++; if (meta.generations >= meta.maxGenerations) { - meta.onDone(meta); clearInterval(interval); + meta.onDone(meta); return; } }, meta.rendersMinimumMilliseconds); diff --git a/src/logic/board.ts b/src/logic/board.ts index f66ae15..559823c 100644 --- a/src/logic/board.ts +++ b/src/logic/board.ts @@ -34,16 +34,18 @@ export const newBoard = (viewWidth: number, viewHeight: number) => { const width = viewWidth + 2; const height = viewHeight + 2; - const newCells = () => new Uint8Array(width * height).fill(DEAD); - const newSkips = () => new Uint8Array(width * height).fill(DONT_SKIP); + const makeZeros = () => { + const buffer = new SharedArrayBuffer(width * height); + return new Uint8Array(buffer); + }; const board: Board = { width, height, - input: newCells(), - output: newCells(), - inSkip: newSkips(), - outSkip: newSkips(), + input: makeZeros(), + output: makeZeros(), + inSkip: makeZeros(), + outSkip: makeZeros(), }; return board; diff --git a/src/logic/next.ts b/src/logic/next.ts index 33f2662..58a6819 100644 --- a/src/logic/next.ts +++ b/src/logic/next.ts @@ -1,5 +1,5 @@ import { Meta } from '../graphics/loop.js'; -import { Board, Cells, DONT_SKIP, SKIP, Skips, SKIP_MULTIPLYER } from './board.js'; +import { Board, Cells, DONT_SKIP, Skip, SKIP, Skips, SKIP_MULTIPLYER } from './board.js'; import { assignBoardPadding } from './padding.js'; export const LOOKUP = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0] as const; @@ -53,13 +53,29 @@ export const next = (board: Board) => { }; export const startNextBoardLoop = (meta: Meta & { loop?: () => void }) => { + // let endI = meta.board.width; + // const segmentSize = (meta.board.height / JOBS + meta.board.height % JOBS) * meta.board.width; + // const segments = Array(JOBS).fill(1).map(() => { + // const beginI = endI; + // endI = Math.min(meta.board.width*(meta.board.height-1), endI + segmentSize) + // return { + // beginI, + // endI, + // }; + // }) const loop = () => { - next(meta.board); - meta.generations++; - - if (meta.generations < meta.maxGenerations) { - setTimeout(loop, 0); - } + meta.workers.forEach(worker => { + const handleWorkerMessage = (event: MessageEvent) => { + worker.removeEventListener('message', handleWorkerMessage); + meta.board = event.data; + meta.generations++; + if (meta.generations < meta.maxGenerations) { + setTimeout(loop, 0); + } + }; + worker.addEventListener('message', handleWorkerMessage); + worker.postMessage(meta.board); + }); }; setTimeout(loop, 0); }; diff --git a/src/logic/worker.ts b/src/logic/worker.ts new file mode 100644 index 0000000..e7e0073 --- /dev/null +++ b/src/logic/worker.ts @@ -0,0 +1,8 @@ +import { Board } from './board.js'; +import { next } from './next.js'; + +addEventListener('message', (event: MessageEvent) => { + const board = event.data; + next(board); + postMessage(board); +}); diff --git a/tsconfig.json b/tsconfig.json index ddeac7a..666c962 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ - "target": "es2015", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "target": "esnext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ @@ -24,7 +24,7 @@ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ /* Modules */ - "module": "es2015", /* Specify what module code is generated. */ + "module": "esnext", /* Specify what module code is generated. */ // "rootDir": "./", /* Specify the root folder within your source files. */ // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */