Offload functions to worker threads with shared memory primitives for Node.js.
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

perf(worker): cache fn-per-id; sync fast-path for trivial args

Two small savings on the worker's per-task hot path:

1. Cache the resolved moroutine function by id. Previously every
task did id.slice() + imported.has() + registry.get() every call;
now it's a single fnCache.get() after the first call per
moroutine. Applies to both handleTask and task-arg resolution.

2. When a task's args contain no MessagePort and no task-args,
skip Promise.all and synchronously map through deserializeArg.
Avoids allocating N promises + awaiting Promise.all for tasks
with primitive args (the common case).

Eliminates ~3 map/string ops and N promise allocations per call
after warmup. Small but measurable gain in dispatch throughput.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+27 -19
+27 -19
src/worker-entry.ts
··· 6 6 7 7 const imported = new Set<string>(); 8 8 const taskCache = new Map<number, unknown>(); 9 + const fnCache = new Map<string, (...args: unknown[]) => unknown>(); 9 10 10 11 function isTaskArg(arg: unknown): arg is { __task__: number; id: string; args: unknown[] } { 11 12 return typeof arg === 'object' && arg !== null && '__task__' in arg; 13 + } 14 + 15 + async function resolveFn(id: string): Promise<(...args: unknown[]) => unknown> { 16 + const cached = fnCache.get(id); 17 + if (cached) return cached; 18 + const url = id.slice(0, id.lastIndexOf('#')); 19 + if (!imported.has(url)) { 20 + await import(url); 21 + imported.add(url); 22 + } 23 + const fn = registry.get(id) as ((...args: unknown[]) => unknown) | undefined; 24 + if (!fn) throw new Error(`Moroutine not found: ${id}`); 25 + fnCache.set(id, fn); 26 + return fn; 12 27 } 13 28 14 29 function portToAsyncIterable<T>(port: MessagePort): AsyncIterable<T> { ··· 78 93 }; 79 94 } 80 95 96 + function needsAsyncResolve(arg: unknown): boolean { 97 + return arg instanceof MessagePort || isTaskArg(arg); 98 + } 99 + 100 + function resolveArgs(args: unknown[]): unknown[] | Promise<unknown[]> { 101 + return args.some(needsAsyncResolve) ? Promise.all(args.map(resolveArg)) : args.map(deserializeArg); 102 + } 103 + 81 104 async function resolveArg(arg: unknown): Promise<unknown> { 82 105 if (arg instanceof MessagePort) { 83 106 return portToAsyncIterable(arg); ··· 86 109 if (taskCache.has(arg.__task__)) { 87 110 return taskCache.get(arg.__task__); 88 111 } 89 - // Resolve the task's own args recursively 90 - const resolvedArgs = await Promise.all(arg.args.map(resolveArg)); 91 - // Import the module and run the function 92 - const url = arg.id.slice(0, arg.id.lastIndexOf('#')); 93 - if (!imported.has(url)) { 94 - await import(url); 95 - imported.add(url); 96 - } 97 - const fn = registry.get(arg.id); 98 - if (!fn) throw new Error(`Moroutine not found: ${arg.id}`); 112 + const resolvedArgs = await resolveArgs(arg.args); 113 + const fn = await resolveFn(arg.id); 99 114 const value = await fn(...resolvedArgs); 100 115 taskCache.set(arg.__task__, value); 101 116 return value; ··· 106 121 parentPort!.on('message', async (msg: { callId?: number; id: string; args: unknown[]; port?: MessagePort }) => { 107 122 const { id, args, port } = msg; 108 123 try { 109 - const url = id.slice(0, id.lastIndexOf('#')); 110 - if (!imported.has(url)) { 111 - await import(url); 112 - imported.add(url); 113 - } 124 + const fn = await resolveFn(id); 114 125 115 - const fn = registry.get(id); 116 - if (!fn) throw new Error(`Moroutine not found: ${id}`); 117 - 118 - const resolvedArgs = await Promise.all(args.map(resolveArg)); 126 + const resolvedArgs = await resolveArgs(args); 119 127 120 128 if (port) { 121 129 let paused = false;