node-multicore
v0.2.2
Published
Thread pool for Node.js, easily make your code run in parallel on multiple cores.
Downloads
6
Maintainers
Readme
Node Multicore
Parallel programming for Node.js made easy. Make any CommonJs or ESM module run in a thread pool.
- Global thread pool—Node Multicore is designed to be a global shared thread pool for all compute intensive NPM packages.
- Create a custom thread pool, or use a global shared one, designed to work across all NPM packages.
- Instant start—dynamic thread pool starts with 0 threads and scales up to the number of CPU cores as the load increases.
- Quickly load modules to the thread pool. Module concurrency is dynamic as well, initially a module is not loaded in any of the threads, as the module concurrency rises, the module is gradually loaded in more worker threads.
- Channels—on each function invocation a bi-directional data channel is created for that function, which allows you to stream data to the function and back.
- Ability to pin a module to a single thread. Say, your thread holds state— you can pin execution to a single thread, making subsequent method call hit the same thread.
- Quickly create a single function, which is loaded in worker threads.
- Dead threads are automatically removed from the thread pool.
- Fash—Node Multicore is as fast or faster as
poolifier
andpiscina
. - Shared thread pool—Node Multicore thread pool is designed to be a global shared thread pool for all compute intensive NPM packages.
Getting started
Install the package
npm install node-multicore
Create a module.ts
that should be executed in the thread pool
import {WorkerFn} from 'node-multicore';
export const add: WorkerFn<[number, number], number> = ([a, b]) => {
return a + b;
};
Load your module from the main thread
import {resolve} from 'path';
import {pool} from 'node-multicore';
const path = resolve(__dirname, 'module');
type Methods = typeof import('./module');
const math = pool.module(path).typed<Methods>();
Now call your methods from the main thread
const result = await math.exec('add', [1, 2]); // 3
Usage guide
- The thread pool
- Modules
- Channels
- Pinning a thread
- Transferring data by ownership
- Anonymous function modules
- Dynamic CommonJs modules
- Creating multicore packages
Loading a module
This is the preferred way to use this library, it will load a module in the thread pool and you can call its exported functions.
Create a module you want to be loaded in the thread pool, put it in a module.ts
file:
export const add = ([a, b]) => a + b;
Now add your module to the thread pool:
import {pool} from 'node-multicore';
const filename = __dirname + '/module';
type Methods = typeof import('./module');
const module = pool.module(filename).typed<Methods>();
You can now call the exported functions from the module:
const result = await module.exec('add', [1, 2]); // 3
Loading a function
This method will create a module out of a single function and load it in the thread pool.
import {fun} from 'node-multicore';
const fn = fun((a: number, b: number) => a + b);
const result = await fn(1, 2); // 3
Channels
Channels are a way to stream data to a function and back. A channel is created for each function call. The channel is a duplex stream, which means you can write to it and read from it.
Dynamic CommonJs modules
You can load a CommonJs module from a string. This is useful if you want to load a module dynamically. It is loaded into threads progressively, as the module concurrency rises. After you are done with the module, you can unload it.
Create a CommonJs text module:
import {pool} from '..';
const text = /* js */ `
let state = 0;
exports.add = ([a, b]) => {
return a + b;
}
exports.set = (value) => state = value;
exports.get = () => state;
`;
Load it using the pool.js()
method:
const module = pool.cjs(text);
Now you can use it as any other module:
// Execute a function exported by the module
const result = await module.exec('add', [1, 2]);
console.log(result); // 3
// Pin module to a single random thread, so multiple calls access the same state
const pinned = module.pinned();
await pinned.ch('set', 123).result;
const get = await pinned.ch('get', void 0).result;
console.log(get); // 123
Once you don't need this module, you can unload it:
// Unload the module, once it's no longer needed
await module.unload();
// await module.exec will throw an error now
Run a demo with the following command:
node -r ts-node/register src/demo/cjs-text.ts
Demo / Benchmark
Run a demo with the following commands:
yarn
yarn demo
The demo executes this work
function on a single core vs.
in the thread pool. The results are printed to the console.
Sample output:
CPU = Apple M1, Cores = 8, Max threads = 7, Node = v18.15.0, Arch = arm64, OS = darwin
Warmup ...
Thread pool: node-multicore (concurrency = 2): 5.280s
Thread pool: piscina (concurrency = 2): 5.214s
Thread pool: worker-nodes (concurrency = 2): 5.255s
Thread pool: node-multicore (concurrency = 4): 3.510s
Thread pool: piscina (concurrency = 4): 2.734s
Thread pool: worker-nodes (concurrency = 4): 2.747s
Thread pool: node-multicore (concurrency = 8): 2.598s
Thread pool: piscina (concurrency = 8): 2.178s
Thread pool: worker-nodes (concurrency = 8): 2.070s
Thread pool: node-multicore (concurrency = 16): 2.144s
Thread pool: piscina (concurrency = 16): 2.158s
Thread pool: worker-nodes (concurrency = 16): 2.045s
Thread pool: node-multicore (concurrency = 32): 1.919s
Thread pool: piscina (concurrency = 32): 2.153s
Thread pool: worker-nodes (concurrency = 32): 2.043s
Thread pool: node-multicore (concurrency = 64): 1.835s
Thread pool: piscina (concurrency = 64): 2.177s
Thread pool: worker-nodes (concurrency = 64): 2.044s
Thread pool: node-multicore (concurrency = 128): 1.843s
Thread pool: piscina (concurrency = 128): 2.145s
Thread pool: worker-nodes (concurrency = 128): 2.046s
Thread pool: node-multicore (concurrency = 256): 1.820s
Thread pool: piscina (concurrency = 256): 2.116s
Thread pool: worker-nodes (concurrency = 256): 2.020s
Thread pool: node-multicore (concurrency = 512): 1.797s
Thread pool: piscina (concurrency = 512): 2.088s
Thread pool: worker-nodes (concurrency = 512): 1.995s
Thread pool: node-multicore (concurrency = 1024): 1.787s
Thread pool: piscina (concurrency = 1024): 2.058s
Thread pool: worker-nodes (concurrency = 1024): 2.003s
Thread pool: node-multicore (concurrency = 1): 9.968s
Thread pool: piscina (concurrency = 1): 9.995s
Thread pool: worker-nodes (concurrency = 1): 10.043s
On main thread (concurrency = 1): 9.616s
On main thread (concurrency = 10): 9.489s