- Spawning an isolated Node.js environment for running a given code
- Accurately timing the execution of a given code (bypassing JIT optimizations which would lead to a biased timing)
- Accurately comparing the timing between two or more algorithms (also, bypassing JIT optimizations which would lead to a biased timing)
Created
February 2, 2025 10:53
-
-
Save diegovgsilva95/80347347758341032662dd6c1528dd0a to your computer and use it in GitHub Desktop.
Node.js - Fair performance testing between two or more algorithms, or simply spawning isolated JS functions/code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { spawn } from "child_process" | |
| import { log } from "console" | |
| process.stdout.write("\x1b[H\x1b[2J\x1b[3J") | |
| const funToSrc = function(fn){ | |
| if(typeof fn !== "function") | |
| throw new TypeError("fn should be a function") | |
| let fns = fn.toString() | |
| return fns.slice(fns.indexOf("{")+1, fns.lastIndexOf("}")) | |
| } | |
| // console.log(`[Main] Main PID = ${process.pid}`) | |
| const termFn = _ => {var __INIT_TIME = performance.now();process.on("beforeExit", _ => {process.send(performance.now()-__INIT_TIME)})} | |
| const runFun = (fn, timeout = 5000) => | |
| new Promise((res) => { | |
| let start = 0 | |
| let reportedDelay = null | |
| let offspring = spawn("node",["--input-type=module", "-"], { | |
| stdio: ["pipe", "inherit", "inherit", "ipc"], | |
| }) | |
| let silverBullet = setTimeout(() => { | |
| // log("[Main] Offspring timeout") | |
| offspring.kill() | |
| silverBullet = null | |
| res({error:"TIMEOUT"}) | |
| }, timeout) | |
| offspring.on("exit", function(code){ | |
| if(silverBullet){ | |
| let outerDelay = performance.now()-start | |
| // log("[Main] Offspring is done") | |
| clearTimeout(silverBullet) | |
| res(code!=0?{error:code}:{reportedDelay, outerDelay}) | |
| } | |
| }).on("message", function(data){ | |
| if(typeof data === "number") | |
| reportedDelay = data | |
| }) | |
| let sourceCode = "" | |
| sourceCode+=funToSrc(termFn) + ";\n" | |
| sourceCode+=funToSrc(fn) | |
| sourceCode=sourceCode.trim(); | |
| // log(`[Main] Source:\n${sourceCode}\n---`) | |
| offspring.stdin.write(sourceCode) | |
| offspring.stdin.end(_ => start = performance.now()) | |
| } | |
| ) | |
| // Example: comparing log2 v. clz32 for determining how many bits a number has | |
| log(`[Main] log2 approach:`, await runFun(async function(){ | |
| let dummyVar = null | |
| for(let k = 0; k < 1E8; k++){ | |
| dummyVar = Math.ceil(Math.log2((k||1)+1)) | |
| } | |
| })) | |
| log(`[Main] clz32 approach:`, await runFun(async function(){ | |
| let dummyVar = null | |
| for(let k = 0; k < 1E8; k++){ | |
| dummyVar = 32-Math.clz32(k||1) | |
| } | |
| })) | |
| // Expected output be like: | |
| //[Main] log2 approach: { reportedDelay: 1558.801457002759, outerDelay: 1597.571946002543 } | |
| //[Main] clz32 approach: { reportedDelay: 132.8723310008645, outerDelay: 168.81133100390434 } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment