Async vs Sync NodeJs: A Simple Benchmark

Posted by Jino Antony on 2020-07-05

Async and sync are probably two of the most heard words among javascript developers, they refer to asynchronous and synchronous programming respectively. Asynchronous programming in javascript can be done using callbacks, Promise, and async and await.

Javascript handles asynchronous tasks with the help of event loop. We know that it increases performance and doesn't block the rest of the code. But everything is theoretical, how do we know is it actually increases performance?

By doing some benchmarks!!

In this article, we won't discuss how to program asynchronously or synchronously, instead, we are going to run some benchmarks against sync and async methods.

For the test case, we are going to use the bcrypt package which has sync and async methods to hash a given string. We also need express.

let's start by installing the dependencies.

yarn add express bcrypt

The following script can be used for benchmarking.

import express from 'express'
import bcrypt from 'bcrypt'

const app = express()

app.get('/sync', (req, res) => {
    let hashed = bcrypt.hashSync('secret', 10)
    return res.send(hashed)
})

app.get('/async', async (req, res) => {
    let hashed = await bcrypt.hash('secret', 10)
    return res.send(hashed)
})

app.listen(3000, () => console.log('Server started on port 3000'))

It's a simple express application with two routes both of them will return the hashed value of string "secret". /sync route will use bcrypt.hashSync() method to hash the value synchronously, while /async route use bcrypt.hash() method to hash the value asynchronously.

We can now run the benchmark test using apache bench.

Apache bench is a tool for benchmarking HTTP servers.

Sync mode benchmark

The following command is used to run the benchmark for sync mode.

ab -k -c 20 -n 250 "http://localhost:3000/sync"

If you don't know what the above command does, it simply calls the apache bench (ab) with the URL to be benchmarked (http://localhost:3000/sync) as input and some options( -k, -c, -n).

  • -k - Enable the HTTP KeepAlive feature
  • -c - Number of multiple requests to perform at a time.
  • -n - Number of requests to perform for the benchmarking session

The result is given below.

Concurrency Level:      20
Time taken for tests:   23.247 seconds
Complete requests:      250
Failed requests:        0
Keep-Alive requests:    250
Total transferred:      66250 bytes
HTML transferred:       15000 bytes
Requests per second:    10.75 [#/sec] (mean)
Time per request:       1859.754 [ms] (mean)
Time per request:       92.988 [ms] (mean, across all concurrent requests)
Transfer rate:          2.78 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   1.6      0      10
Processing:    90 1783 414.3   1804    3655
Waiting:       90 1783 414.4   1804    3655
Total:         90 1783 414.5   1804    3655

Async mode benchmark

Now let's benchmark the asynchronous mode.

ab -k -c 20 -n 250 "http://localhost:3000/async"

And the results are as follows.

Concurrency Level:      20
Time taken for tests:   10.141 seconds
Complete requests:      250
Failed requests:        0
Keep-Alive requests:    250
Total transferred:      66250 bytes
HTML transferred:       15000 bytes
Requests per second:    24.65 [#/sec] (mean)
Time per request:       811.281 [ms] (mean)
Time per request:       40.564 [ms] (mean, across all concurrent requests)
Transfer rate:          6.38 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.5      0       3
Processing:    97  776 120.5    802     874
Waiting:       95  776 120.6    802     874
Total:         97  776 120.3    802     874

Comparison

Metrics sync async
Concurrency Level 20 20
Time taken for tests: 23.247 seconds 10.141 seconds
Complete requests: 250 250
Failed requests: 0 0
Keep-Alive requests: 250 250
Total transferred: 66250 bytes 66250 bytes
HTML transferred: 15000 bytes 15000 bytes
Requests per second: 10.75 [#/sec] (mean) 24.65 [#/sec] (mean)
Time per request: 1859.754 [ms] (mean) 811.281 [ms] (mean)
Time per request: 92.988 [ms] (mean, across al 40.564 [ms] (mean, across all concurrent requests)
Transfer rate: 2.78 [Kbytes/sec] received 6.38 [Kbytes/sec] received

As you can see, async mode performs better than sync mode. The async mode handles more request/sec than sync mode and its time/request is less compared to that of sync mode.

Conclusion

As per the benchmark results, async mode performs better than sync mode when performing I/O (even though the test case doesn't perform any I/O operations). Hashing using bcrypt is a CPU intensive operation and when hashing strings using bcrypt in async mode, it uses the thread pool and doesn't block the event loop.

Always use async mode when your code needs to perform some blocking I/O operations as it doesn't block the event loop.