Hello, I'm Conard Li. Today we'll look at an interesting topic on node JS, will it make the service faster?

As we all know, Nodejs is suitable for I/O-Intensive tasks, but not for CPU intensive tasks. At the same time, we have many ways to handle such tasks (sub process / cluster, worker thread). In addition, it is possible to use other languages (C, C + +, Rust, Golang) as separate services / microservices or call them through WebAssembly scripts.
This article is not a node JS and Golang, but in node From the perspective of JS development services, try to introduce Golang (let it perform some CPU intensive operations) in some scenarios to see if it will be faster.
I also wrote an article about the introduction of Rust into the React project. If you are interested, you can see: Write faster React components using Rust
Recently, I found a foreigner doing it in node Performance test of introducing Golang into JS service( https://blog.devgenius.io/node-js-in-go-we-trust-7da6395776f2 ), very interesting, so I translated it.
Test item
- Try using node only JS to solve CPU intensive tasks
- Create a separate service written by Golang and connect it to the application by sending a request or message queue
- Build a wasm file using Golang to run node Some methods in JS
Speed and money
I am a fan of old-fashioned Italian westerns, especially The Good, the Bad and the Ugly. In this article, we have three test items corresponding to three heroes in the film.
Node.js (good man)

advantage:
- The front and back ends use the same language
- I/O master - ultrafast event loop
- Largest Arsenal - npm
Golang (bad man)

advantage:
- Designed by Google
- Almost all operating systems support
- "Goroutines" - a special function in Golang that can run simultaneously with other functions or methods (suitable for CPU intensive tasks)
- Simple - only 25 keywords
Nodejs golang / web assembly (ugly people)

advantage:
- used anywhere
- Supplementary JavaScript
- You can write code in different languages and use it in JavaScript wasm script
Finally, let's focus on this test item:
By setting the operating system to "js" and the schema to "wasm" ` ` (all GOOS and GOARCH ` values are listed here https://go.dev/doc/install/source#environment ), you can build Golang code as Wasm file:
GOOS=js GOARCH=wasm go build -o main.wasm
To run the compiled Go code, you need wasm_ exec. glue code in JS. It can be found here:
${GOROOT}/misc/wasm/wasm_exec.js
For instantiation, I used @ assemblyscript/loader and created a nodejs golang module (by the way, @ assemblyscript/loader is its only dependency). This module helps to create, build, and run separate wasm scripts or functions that can be used in JavaScript code
require('./go/misc/wasm/wasm_exec'); const go = new Go(); ... const wasm = fs.readFileSync(wasmPath); const wasmModule = await loader.instantiateStreaming(wasm, go.importObject); go.run(wasmModule.instance);
Incidentally, other languages can be used to create in the same way wasm file.
C: emcc hello.c -s WASM=1 -o code Secret Garden.html C++: em++ hello.cpp -s WASM=1 -o code Secret Garden.html Rust: cargo build --target wasm --release

Let's see who is the fastest gun in the Wild West
To do this, we need to create 2 servers
1.Golang server

package main import ( ... "fmt" ... "net/http" ... ) func main() { ... fmt.Print("Golang: Server is running at http://localhost:8090/") http.ListenAndServe(":8090", nil) }
2. Node.js server

const http = require('http'); ... (async () => { ... http.createServer((req, res) => { ... }) .listen(8080, () => { console.log('Nodejs: Server is running at http://localhost:8080/'); }); })();
We will test the execution time of each task. Note:
- For the Golang server, its delay will be the direct execution time of the function + the network request delay
- For node JS and WebAssembly, which will only be the execution time of the function
The final duel
1. "ping" request
Just check how long a request will take to execute

Node.js
const nodejsPingHandler = (req, res) => { console.time('Nodejs: ping'); const result = 'Pong'; console.timeEnd('Nodejs: ping'); res.statusCode = 200; res.setHeader('Content-Type', 'application/json'); res.write(JSON.stringify({ result })); res.end(); };
Golang
// golang/ping.js const http = require('http'); const golangPingHandler = (req, res) => { const options = { hostname: 'localhost', port: 8090, path: '/ping', method: 'GET', }; let result = ''; console.time('Golang: ping'); const request = http.request(options, (response) => { response.on('data', (data) => { result += data; }); response.on('end', () => { console.timeEnd('Golang: ping'); res.statusCode = 200; res.setHeader('Content-Type', 'application/json'); res.write(JSON.stringify({ result })); res.end(); }); }); request.on('error', (error) => { console.error(error); }); request.end(); };
// main.go func ping(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "Pong") }
nodejs-golang
// nodejs-golang/ping.js const nodejsGolangPingHandler = async (req, res) => { console.time('Nodejs-Golang: ping'); const result = global.GolangPing(); console.timeEnd('Nodejs-Golang: ping'); res.statusCode = 200; res.setHeader('Content-Type', 'application/json'); res.write(JSON.stringify({ result })); res.end(); };
// main.go package main import ( "syscall/js" ) func GolangPing(this js.Value, p []js.Value) interface{} { return js.ValueOf("Pong") } func main() { c := make(chan struct{}, 0) js.Global().Set("GolangPing", js.FuncOf(GolangPing)) <-c }
result:

Simple summation of two numbers

Node.js
const result = p1 + p2;
Golang
func sum(w http.ResponseWriter, req *http.Request) { p1, _ := strconv.Atoi(req.URL.Query().Get("p1")) p2, _ := strconv.Atoi(req.URL.Query().Get("p2")) sum := p1 + p2 fmt.Fprint(w, sum) }
nodejs-golang
func GolangSum(this js.Value, p []js.Value) interface{} { sum := p[0].Int() + p[1].Int() return js.ValueOf(sum) }
result

Calculate the Fibonacci sequence (the 100000th number)

Node.js
const fibonacci = (num) => { let a = BigInt(1), b = BigInt(0), temp; while (num > 0) { temp = a; a = a + b; b = temp; num--; } return b; };
Golang
func fibonacci(w http.ResponseWriter, req *http.Request) { nValue, _ := strconv.Atoi(req.URL.Query().Get("n")) var n = uint(nValue) if n <= 1 { fmt.Fprint(w, big.NewInt(int64(n))) } var n2, n1 = big.NewInt(0), big.NewInt(1) for i := uint(1); i < n; i++ { n2.Add(n2, n1) n1, n2 = n2, n1 } fmt.Fprint(w, n1) }
nodejs-golang
func GolangFibonacci(this js.Value, p []js.Value) interface{} { var n = uint(p[0].Int()) if n <= 1 { return big.NewInt(int64(n)) } var n2, n1 = big.NewInt(0), big.NewInt(1) for i := uint(1); i < n; i++ { n2.Add(n2, n1) n1, n2 = n2, n1 } return js.ValueOf(n1.String()) }
result

Calculate md5 (10k string)

Node.js
const crypto = require('crypto'); const md5 = (num) => { for (let i = 0; i < num; i++) { crypto.createHash('md5').update('nodejs-golang').digest('hex'); } return num; };
Golang
func md5Worker(c chan string, wg *sync.WaitGroup) { hash := md5.Sum([]byte("nodejs-golang")) c <- hex.EncodeToString(hash[:]) wg.Done() } func md5Array(w http.ResponseWriter, req *http.Request) { n, _ := strconv.Atoi(req.URL.Query().Get("n")) c := make(chan string, n) var wg sync.WaitGroup for i := 0; i < n; i++ { wg.Add(1) go md5Worker(c, &wg) } wg.Wait() fmt.Fprint(w, n) }
nodejs-golang
func md5Worker(c chan string, wg *sync.WaitGroup) { hash := md5.Sum([]byte("nodejs-golang")) c <- hex.EncodeToString(hash[:]) wg.Done() } func GolangMd5(this js.Value, p []js.Value) interface{} { n := p[0].Int() c := make(chan string, n) var wg sync.WaitGroup for i := 0; i < n; i++ { wg.Add(1) go md5Worker(c, &wg) } wg.Wait() return js.ValueOf(n) }
result

Calculate sha256 (10k string)

Node.js
const crypto = require('crypto'); const sha256 = (num) => { for (let i = 0; i < num; i++) { crypto.createHash('sha256').update('nodejs-golang').digest('hex'); } return num; };
Golang
func sha256Worker(c chan string, wg *sync.WaitGroup) { h := sha256.New() h.Write([]byte("nodejs-golang")) sha256_hash := hex.EncodeToString(h.Sum(nil)) c <- sha256_hash wg.Done() } func sha256Array(w http.ResponseWriter, req *http.Request) { n, _ := strconv.Atoi(req.URL.Query().Get("n")) c := make(chan string, n) var wg sync.WaitGroup for i := 0; i < n; i++ { wg.Add(1) go sha256Worker(c, &wg) } wg.Wait() fmt.Fprint(w, n) }
nodejs-golang
func sha256Worker(c chan string, wg *sync.WaitGroup) { h := sha256.New() h.Write([]byte("nodejs-golang")) sha256_hash := hex.EncodeToString(h.Sum(nil)) c <- sha256_hash wg.Done() } func GolangSha256(this js.Value, p []js.Value) interface{} { n := p[0].Int() c := make(chan string, n) var wg sync.WaitGroup for i := 0; i < n; i++ { wg.Add(1) go sha256Worker(c, &wg) } wg.Wait() return js.ValueOf(n) }
result

final result

- Node.js, can complete its work well
- Golang can do its job well
- The web assembly (and now my nodejs golang module) does its job well
- Golang can be used as a stand-alone application, as a service / micro service, as a source of wasm scripts, and then can be called in JavaScript
- 5 Node.js and Golang both have ready-made mechanisms to use web assembly in JavaScript
conclusion
Fast is good, but accuracy is everything- Wyatt Earp
- If possible, do not use node JS to run CPU intensive tasks - it's best not to do this
- If you need to be in node JS - you can try to use node first JS to perform this operation, the performance may not be as bad as you think
- Between performance (using other languages) and readability, it is best to choose readability. If you are the only person familiar with the language, adding this new language to the project is not a good idea
- For me, it's best to "keep separate" services in different languages. You can try to create separate services or microservices for CPU intensive computing, which can be easily extended
- First of all, web assembly is very good for browsers. Wasm binary code is smaller than JS code, easier to parse, backward compatible, etc., but it may not be a good choice in Node services