- Notifications
You must be signed in to change notification settings - Fork6
Non-blocking Concurrent Computation for JavaScript RTEs (Web Browsers, Node.js, Deno & Bun)
License
BitairLabs/concurrent.js
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Change Log |Star History |Community
Concurrent.js is a library that enables non-blocking computation on JavaScript RTEs by dynamically loading a module into a background thread.
- Sharing workers
- Parallel execution
- Reactive concurrency
- Isolation
- Built upon web workers (a.k.a. worker threads).
- Simplifies the complexity of worker usage by providing a minimal API.
- Automatically creates and terminates workers.
- Automatically cleans up a worker's memory.
- Has no third-party runtime dependency.
- Written in TypeScript with the strictest ESNext config.
- Strictly designed to support strongly-typed programming.
- Packaged as platform-specific bundles that target ES2020.
Save and run thehello world script to see Concurrent.js in action:
bash hello_world.sh
npm i @bitair/concurrent.js
At its highest level of design, Concurrent.js is a dynamic module importer that loads a module into a web worker:
import{concurrent}from'@bitair/concurrent.js'// In Deno// import { concurrent } from 'https://deno.land/x/concurrentjs@v0.8.2/mod.ts'// Import a moduleconstMyModule=concurrent.import(newURL('./sample_module.js',import.meta.url))// In a CommonJS module// const MyModule = concurrent.import(path.join(__dirname, 'sample_module.js'))// Load it into a web workerconst{ SampleObject, sampleFunction}=awaitMyModule.load()// Load it into another web worker// const { SampleObject: SampleObject2, sampleFunction: sampleFunction2 } = await MyModule.load()// Run a functionconstresult=awaitsampleFunction(/*...args*/)// Run a class (instance members)constobj=awaitnewSampleObject(/*...args*/)// Instantiateconstvalue=awaitobj.sampleProp// Get a field or getterawait((obj.sampleProp=1),obj.sampleProp)// Set a field or setterconstresult=awaitobj.sampleMethod(/*...args*/)// Call a method// Run a class (static members)constvalue=awaitSampleObject.sampleStaticProp// Get a static field or getterawait((SampleObject.sampleStaticProp=1),SampleObject.sampleStaticProp)// Set a static field or setterconstresult=awaitSampleObject.sampleStaticMethod(/*...args*/)// Call a static method// Terminate Concurrent.jsawaitconcurrent.terminate()
Browser
Node & Bun
Deno
The following results demonstrate the average execution time and CPU usage of running 10 concurrent calculations (10 iterations) of the factorial of 50,000 on various JavaScript runtime environments (RTEs). These calculations were performed on a Quad-core AMD APU with a base clock rate of 2.2GHz within a freshly installed isolated Ubuntu VM.
(There are 213,237 digits in the factorial of 50,000)
| RTE | JS Engine | Execution Time | CPU Usage | |
|---|---|---|---|---|
| 1 | Deno (v1.40) | V8 | 7.9168s | 100% |
| 2 | Chrome* (v121.0) | V8 | 7.919s | 100% |
| 3 | Node (v20.11) | V8 | 8.117s | 100% |
| 4 | Servo (v0.0.1-c94d584) | SpiderMonkey | 31.267s | 99% |
| 5 | LibreWolf (122.0) | SpiderMonkey | 35.417s | 92% |
| 6 | Firefox* (v125.0) | SpiderMonkey | 49.061s | 95% |
| 7 | Bun (v1.0.26) | JavaScriptCore | 51.502s | 99% |
| 8 | GNOME Web (v45.2) | JavaScriptCore | 59.058s | 75% |
- A headless environment was used for benchmarking.
To benchmark Node, Deno, Bun RTEs as well as Chrome and Firefox browsers use thebenchmarking app:
git clone https://github.com/bitair-org/concurrent.js.gitcd concurrent.js/apps/benchmarknpm inpm start# This command starts a web server required by the headless browsers. Do not open the http://127.0.0.1:8080 addressnpm run benchmark
For benchmarking other browsers, use thebrowser basic usage sample
git clone https://github.com/bitair-org/concurrent.js.gitcd concurrent.js/apps/sample/browsernpm inpm start# Open the http://127.0.0.1:8080 address in the target browser
To run each function call or object instance on a separate CPU core, theload method of the imported module must be called for each function call or object instance individually:
import{concurrent}from'@bitair/concurrent.js'constextraBigint=concurrent.import('extra-bigint')concurrent.config({maxThreads:16})// Instead of a hardcoded value, use os.availableParallelism() in Node.js v19.4.0 or laterconsttasks=[]for(leti=0;i<=100;i++){const{ factorial}=awaitextraBigint.load()tasks.push(factorial(i))}constresults=awaitPromise.all(tasks)// ...rest of the codeawaitconcurrent.terminate()
The reactive concurrency feature provides a bidirectional channel for messaging. A message can be replied to by returning a value:
services/index.mjs
// import type { IChannel } from '@bitair/concurrent.js'exportasyncfunctionreactiveAdd(channel/*: IChannel */){letdone=falseletsum=0leti=0channel.onmessage(name=>{if(name==='done')done=true})do{sum+=awaitchannel.postMessage('next',i++)}while(!done)returnsum}
index.mjs
import{concurrent,Channel}from'@bitair/concurrent.js'const{ reactiveAdd}=awaitconcurrent.import(newURL('./services/index.mjs',import.meta.url)).load()constchannel=newChannel((onmessage,postMessage)=>{constarr=[1,2,3,4]onmessage(async(name, ...data)=>{if(name==='next'){const[i]=dataif(i===arr.length-1)awaitpostMessage('done')returnarr[i]}})})constresult=awaitreactiveAdd(channel)// ...rest of the codeawaitconcurrent.terminate()
concurrent.import<T>(src:URL|string):IConcurrentModule<T>
Prepares a module to be loaded into workers. Note that only functions and classes can be imported.
src: URL | stringSource of the module. Must be either a URL or a package name. Note that passing a package name is only applicable in Node.js.
IConcurrentModule<T>.load() :Promise<T>
Loads the module into a worker.
concurrent.config(settings:ConcurrencySettings):void
Configures the global settings of Concurrent.js.
settings: ConcurrencySettingssettings.maxThreads: number [default=1]The maximum number of available threads to be spawned.
settings.threadIdleTimeout: number | typeof Infinity [default=Infinity]Number of minutes before Concurrent.js terminates an idle thread.
settings.minThreads: number [default=0]The number of threads created when Concurrent.js starts and kept alive to avoid thread recreation overhead.
concurrent.terminate(force?:boolean):Promise<void>
Terminates Concurrent.js.
force?: boolean [Not implemented]Forces Concurrent.js to exit immediately without waiting for workers to finish their tasks.
classChannelimplementsIChannel
Used to send/receive messages to/from functions and methods (instance or static). Note that a function or method can only have one channel argument and it must be the last argument. The channel object cannot be reused to call another function or method.
constructor(listener:(onmessage:Channel['onmessage'],postMessage:Channel['postMessage'])=>void)
onmessage(handler:(name:string|number, ...data: unknown[])=>unknown):void
Sets the event handler for receiving a message. The handler should return a value if a reply is required for the message.
postMessage(name:string|number, ...data: unknown[]):Promise<unknown>
Sends a message to the other end and returns its reply.
About
Non-blocking Concurrent Computation for JavaScript RTEs (Web Browsers, Node.js, Deno & Bun)
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Uh oh!
There was an error while loading.Please reload this page.