Running CPU Intensive task in Nodejs (2024)

Badewa kayode

Posted on

Running CPU Intensive task in Nodejs (3) Running CPU Intensive task in Nodejs (4) Running CPU Intensive task in Nodejs (5) Running CPU Intensive task in Nodejs (6) Running CPU Intensive task in Nodejs (7)

#node #eventloop #oldpost

Moving my articles from Medium to Dev.to

This article was originally posted here:

The code for the article can be found here.

Nodejs is good for IO intensive tasks but bad for CPU intensive tasks. The reason Nodejs is bad for CPU intensive task is that it runs on the event loop, which runs on a single thread.

The event loop is responsible for everything that runs on the user-land of Nodejs. This event loop runs on a single thread. When this thread is blocked all other tasks would have to wait for the thread to be unlocked before they can be executed.

I am not an expert on this issue, I am only giving a way in which I achieved this, so if anyone has something to add or some corrections to make about the post I’m open to advice.

Running Fibonacci

In this article, I would be using Fibonacci as my CPU intensive task (it takes time to get the Fibonacci number of numbers above 45). I am going to create a server that serves
a simple response for any URL that does not match /fibo, and when the URL matches /fibo I will serve a Fibonacci result.

In this article I will not use any npm module; I will just be using core node modules in this article.

The Server

The server for this article would only return two types of response:

  • A Fibonacci number for the req.headers.fibo value when the URL route is equal to fibo
  • A hello world string for any URL route that does not equal fibo

Lets run the fibo normally

First to show how Fibonacci blocks the event loop, I will create a server that serves a Fibonacci that runs on the same process as the simple hello world response.

Create a file called fibo_in_server.js. This file would return the Fibonacci number of a number passed into the
req.headers.fibo when the URL route is equal to the /fibo and return’s hello world for any other URL match.

 const http = require("http"); function fibo(n) { if (n < 2) return 1; else return fibo(n - 2) + fibo(n - 1); } const server = http.createServer((req, res) => { "use strict"; if (req.url == '/fibo') { let num = parseInt(req.headers.fibo); console.log(num) res.end(`${fibo(num)}`) } else { res.end('hello world'); } }); server.listen(8000, () => console.log("running on port 8000"));

We can run the above code and check the response. When the req.url is not /fibo the response is hello world and the Fibonacci number of the number passed into the header fibo field for a req.url that is equal to /fibo.

I’m using the Postman Chrome extension for requesting the server.

If we send a number like 45 to the server, the request would block the event loop until it is done getting the Fibonacci number. Any request to get the hello world string would have to wait until the long-running Fibonacci is done.

This is not good for users who want to get only a simple response, because they have to wait for the Fibonacci response to be completed.

In this article, what I am going to do is look at some ways to fix this problem. I am not a Pro Super NodeJs Guru User, but I can give some methods of dealing with this problem.

Methods of dealing with this problem

  • running Fibonacci in another Nodejs process
  • using method 1 with a batch queue to process the Fibonacci
  • using method 2 with a pool to manage the processes

Method 1: Running in another process

What we can do is run the Fibonacci function in another Nodejs process. This would prevent the event loop from getting blocked by the Fibonacci function.

To create another process we use the [child_process]() module. I am going to create a file, fibonacci_runner.js, that runs as the child
process, and another file called server_method1.js, the parent process.

The server_method1.js serves the response to the client. When a request to the /fibo is made the server gives the work to its child process fibo_runner.js to
handle. This prevents the event loop on the server from getting blocked, making it easier for a smaller request to be handled.

Here is the code for fibonacci_runner.js

process.on("message", (msg) => { "use strict"; process.send({value: fibo(parseInt(msg.num)),event:msg.event})});function fibo(n) { // 1 if (n < 2) return 1; else return fibo(n - 2) + fibo(n - 1)}

And here is the code for server_method1.js:

const http = require("http");const {fork} = require('child_process');const child = fork(`${__dirname}/fibonacci_runner.js`);let {EventEmitter} = require('events');let event = new EventEmitter();const server = http.createServer(function(req, res){ if (req.url == '/fibo') { let rand = Math.random() * 100; //generate a random number child.send({num:req.headers.fibo,event:rand}); //send the number to fibonacci_running event.once(rand, (value) => { //when the event is called res.end(`${value}`) }) } else { res.end('hello world'); }});child.on("message",(msg)=> event.emit(msg.event,msg.value)); //emit the event event sentserver.listen(8000, () => console.log("running on port 8000"));

Now if we visit the URL route /fibo with a value >= 45 in the req.headers.fibo value, it won’t block the request for the hello world. Better than what we had before.

The next step is to reduce the amount of computation the fibonacci_runner does. One way of reducing this is by using a batch queue with/or a cache (Note:
there are still other methods of doing this).

In this article, I am going to discuss the batch queue alone.

You can check out these articles to know more about the cache :

https://community.risingstack.com/redis-node-js-introduction-to-caching/amp/
https://goenning.net/2016/02/10/simple-server-side-cache-for-expressjs/

Method 2: Batching queue

When dealing with asynchronous operations, the most basic level of caching can be achieved by batching together a set of invocations to the same API. The idea is very simple: if I am invoking an asynchronous function while there is still another one pending, we can attach the callback to the already running operation, instead of Creating a brand new request. — “Nodejs Design Patterns”

From the definition above, we want to batch requests with the same req.headers.fibo value together, Instead of calling a new Fibonacci call while one with the same req.headers.fibo value
is still pending.

I am still going to use the fibonacci_runner.js to run the Fibonacci operation, but I’m going to create a new file, server_method2.js, that has
an asyncBatching function that sits between the fibonacci_runner.js and the call to process the req.headers.fibo.

Here is the code for server_method2.js

const http = require("http");const {fork} = require('child_process');const child = fork(`${__dirname}/fibonacci_runner.js`);let Queue = {}//1function asyncBatching(num, cb) { if (Queue[num]) { Queue[num].push(cb) //2 } else { Queue[num] = [cb]; //3 child.send({num: num, event: num})//4 }}const server = http.createServer(function (req, res) { if (req.url == '/fibo') { const num = parseInt(req.headers.fibo) asyncBatching(num,(value)=>res.end(`${value}`)) } else { res.end('hello world'); }});child.on("message", (msg) =>{ "use strict"; let queue = [...Queue[msg.event]]; Queue[msg.event] = null; //empty the Queue queue.forEach(cb=>cb(msg.value)) console.log(`done with ${msg.event}`)});server.listen(8000, () => console.log("running on port 8000"));

I would use the Apache benchmark to run this test

$ ab -n 10 -c 10 -H 'fibo: 39' http://localhost:8000/fibo

It takes 3.196 on my machine for method2,and 32.161 for method1. This means method2 responds n times faster than method1
(number of concurrent users sending the same req.headers.fibo value).

To improve method2 further we can use a cache to save the value of the Fibonacci but am not going to touch caching in
this article :(.

What is going to do here is improve on method2 by increasing the number of child processes. I am going to use a pool that
would manage the distribution of work among the child processes.

Method 3: Pooling and managing multiple processes

Creating multiple child processes to handle the Fibonacci operation would make it respond faster and better. You have to know that running many processes is making
use of system resources. Creating too many processes is bad; Just create enough.

The Pool is responsible for handling child processes. First, let’s create a Pool file, Pool.js, that exports a Pool class.

Code for Pool.js file:

const child = require('child_process');class Pool { constructor(file, maxPool, messageCb) { this.pool = []; this.active = []; this.waiting = []; this.maxPool = maxPool; let releaseWorker = (function (worker) { //move the worker back to the pool array this.active = this.active.filter(w => worker !== w); this.pool.push(worker); //if there is work to be done, assign it if (this.waiting.length > 0) { this.assignWork(this.waiting.shift()) } }).bind(this); for (let i = 0; i < maxPool; i++) { let worker = child.fork(file); worker.on("message", (...param) => { messageCb(...param); releaseWorker(worker) }); this.pool.push(worker) } } assignWork(msg) { if (this.active.length >= this.maxPool) { this.waiting.push(msg); console.log(this.waiting) } if (this.pool.length > 0) { let worker = this.pool.pop(); worker.send(msg); this.active.push(worker) } }}module.exports = Pool;

The Pool class

As said before, the Pool is responsible for handling the child process. It has only one method, the assignWorker method. The assignWorker method
assigns work to a worker (child process) to handle. If all the workers are busy the work would be done as soon as one is free.

The Pool Object takes three parameters on creation. These arguments are :

  • the file to run as the child process
  • the number of processes to create
  • the function to call when the workers send a message back

Now let’s create server_method3.js file that makes use of the Pool Object.

The code for server_method3.js:

const http = require("http");let Queue = {};const Pool = require("./Pool");let Pooler = new Pool(`${__dirname}/fibonacci_runner.js`,2, (msg) => { "use strict"; let queue = [...Queue[msg.event]]; Queue[msg.event] = null; //empty the Queue queue.forEach(cb => cb(msg.value)); console.log(`done with ${msg.event}`)});//responsible for batchingfunction asyncBatching(num, cb) { if (Queue[num]) { Queue[num].push(cb) } else { Queue[num] = [cb]; Pooler.assignWork({num: num, event: num}) }}const server = http.createServer(function (req, res) { if (req.url == '/fibo') { const num = parseInt(req.headers.fibo); asyncBatching(num, (value) => res.end(`${value}`)) //  } else { res.end('hello world'); }});server.listen(8000, () => console.log("running on port 8000"));

server_methodw3.js runs more than one child process, so we can run multiple Fibonacci operations at the same time,
instead of waiting for the one to finish.

The number of Fibonacci we can run at the same time depends on the number passed as the second parameter to the Pool
constructor.

Note: limit the number of processes you spawn ups.

Conclusion

Running heavy task on node event loop is a bad idea, and remember to pass the task to another process to handle, be it Nodejs or not (you can start a C++ to handle
very heavy operations).

Remember to always keep the event loop from getting blocked by any operation.

Read this article for more about the Event Loop.

Badewa Kayode, peace out :).

Running CPU Intensive task in Nodejs (2024)

FAQs

Running CPU Intensive task in Nodejs? ›

However, when it comes to handling CPU-intensive tasks, the single-threaded nature of Node. js might block the main thread from taking any more requests to process. This is where the concept of multithreading comes to the rescue, enabling you to efficiently manage compute-heavy workloads.

Is node JS good for CPU-intensive tasks? ›

A: Node. js is not the best choice for CPU-intensive tasks due to its single-threaded nature. Technologies like Java or Python are more suitable for such tasks.

How would you efficiently create a node.js worker process to handle CPU-intensive tasks? ›

Since Node. js v10. 5.0, using worker threads has been the recommended approach for improving the performance of CPU-bound tasks. Worker threads allow you to perform CPU-bound tasks concurrently, leveraging multiple CPU cores within a single Node.

What are CPU-intensive tasks? ›

CPU-intensive computing has been developed for solving mathematical problems that are too difficult for computers to solve using standard computational techniques.

Can we utilize multiple CPU in NodeJS? ›

Even if the computer has more than one CPU core, Node. js does not use all of them by default. It only uses one CPU core for the main thread that handles the event loop.

What is NodeJS not recommended for? ›

Not Ideal for CPU-intensive tasks

Its event-driven nature makes applications highly scalable. But if your application has to run tasks that are CPU-intensive and heavy computing, Node. js might not be the best choice for you. Because when a heavy task is running, it blocks the Node.

Is NodeJS overkill? ›

Like cutting an onion with a machete, Node. js is overkill for small, hobby projects. If you are working on a small project, that doesn't require a lot of server-side processing, you may consider more simple alternatives like Ruby or even (now almost prehistoric) PHP.

How to optimize Node.js performance? ›

2. Tips to Improve Node JS Performance
  1. 2.1 Monitor & Measure App Performance. ...
  2. 2.2 Reduce Latency Time Through Caching. ...
  3. 2.3 Optimize Your Data Handling Methods. ...
  4. 2.4 Load Balancing. ...
  5. 2.5 Use Timeouts. ...
  6. 2.6 Monitor in Real-Time. ...
  7. 7 Improve Throughput by Cluster. ...
  8. 2.8 Employ HTTP/2 and SSL/TLS to Make Web Browsing Faster.
Nov 3, 2023

How to run Node.js faster? ›

Consider these tips to elevate your Node.js application's performance:
  1. Optimize Your Code. ...
  2. Use a Caching Layer. ...
  3. Use Compression. ...
  4. Use Load Balancing. ...
  5. Use a Content Delivery Network (CDN) ...
  6. Optimize Database Queries. ...
  7. Use a Reverse Proxy. ...
  8. Use HTTP/2.
Jun 12, 2023

How do I speed up my node build? ›

7 Ways to Speed Up Your Node. js Development Process
  1. Utilize Typescript. By introducing types, TypeScript expands JavaScript. ...
  2. Utilize Cache. ...
  3. Go Asynchronous. ...
  4. Make Use of Gzip Compression. ...
  5. Parallelize. ...
  6. Monitor in Real-Time. ...
  7. Look Deeper.
Jun 11, 2022

What is an example of a CPU intensive workload? ›

3D rendering is a notoriously CPU-intensive workload; rendering a single image can take minutes on a single CPU core. Rendering 250K images can consume 30K+ CPU hours.

Is coding a CPU intensive task? ›

Most programming languages are high-level, so CPU doesn't matter, but if you were programming a compiler, for instance, you would care what CPU with which you were dealing, because the “vocabulary”, or set of available machine-language commands, is different between different CPUs.

What makes an application CPU intensive? ›

Compute-Intensive (or CPU-Intensive) Processes

Compute-intensive processes are ones that perform IO rarely, perhaps to read in some initial data from a file, for example, and then spend long periods processing the data before producing the result at the end, which requires minimal output.

How to get CPU utilization in NodeJS? ›

cpuUsage() Method. The process. cpuUsage() method is an inbuilt application programming interface of the Process module which is used to get the user, system CPU time usage of the current process. It is returned as an object with property user and system, values are in microseconds.

Can node.js be multithreaded? ›

Yes! The reality is that we can already do background processing in Node. js. We can fork the process and do exactly that using message passing, which you can imagine as simply as passing a message from one process to another.

Why is node using so much CPU? ›

Huge payloads from Node. js services also can be a problem, because Node. js stringifies objects to JSON first and then sends them to the client. All of these operations can cause high CPU, make sure that payload size is not huge, use pagination, and don't prepopulate unnecessary data.

Is NodeJS good for heavy computation? ›

js receives a CPU-bound task: Whenever a heavy request comes to the event loop, Node. js would set all the CPU available to process it first, and then answer other requests queued. That results in slow processing and overall delay in the event loop, which is why Node. js is not recommended for heavy computation.

Which programming language is best for CPU intensive tasks? ›

The 10 Best Programming Languages to Learn
  1. Python. Python is a high-level, general-purpose programming language. ...
  2. C# C# is an object-oriented programming language – a model that organizes software design around objects. ...
  3. C++ ...
  4. JavaScript. ...
  5. PHP. ...
  6. Swift. ...
  7. Java. ...
  8. Go.
Mar 14, 2024

Is NodeJS good for data-intensive applications? ›

Node. js is well-suited for real-time data streaming and analytics applications, where data needs to be processed and analyzed as it arrives. With its event-driven architecture and non-blocking I/O operations, Node. js can efficiently handle multiple concurrent connections and process incoming data in real-time.

Top Articles
Latest Posts
Article information

Author: Clemencia Bogisich Ret

Last Updated:

Views: 6613

Rating: 5 / 5 (60 voted)

Reviews: 91% of readers found this page helpful

Author information

Name: Clemencia Bogisich Ret

Birthday: 2001-07-17

Address: Suite 794 53887 Geri Spring, West Cristentown, KY 54855

Phone: +5934435460663

Job: Central Hospitality Director

Hobby: Yoga, Electronics, Rafting, Lockpicking, Inline skating, Puzzles, scrapbook

Introduction: My name is Clemencia Bogisich Ret, I am a super, outstanding, graceful, friendly, vast, comfortable, agreeable person who loves writing and wants to share my knowledge and understanding with you.