I found a comparison of nodejs and go performance on this site (https://www.techempower.com/benchmarks/#section=data-r23&test=update&l=zijo5b-pa5). I was interested in the fact that go loses to nodejs. Could there be something wrong there?
Just want to know the community opinion: from what I see the key benefit of using node.js for back-end is the same language as front-end part. If I would like to work on back-end side only, would it better to switch to golang, let’s say, to do back-end only things, and don’t care about any front-end related stuff. Have node.js developers already considered as the real “back-end” guys? Or just an JavaScript front-end switchers 😀
Videos
Hi Reddit!
I know that this is a NodeJS thread, but I want to know your opinion about Go and NodeJS. What do you think about it?
Which of these two languages would you recommend to your friend and why? What do you think is more in demand now and for the knowledge of which of these technologies they pay more?
I am a NodeJS developer and I really love NodeJS, but also I wanna know your opinion too. I will be grateful for any opinions, thanks!
Hey folks,
we're trying to benchmark our existing Node.js solution for fetching messages from an AWS SQS queue against a Go implementation, hoping that we could achieve the same performance with less resources in Go.
On my local MacBook Pro with an M1 Pro, the Node.js application using 50 workers pulls and removes >6,000 messages per second from the queue. The following Go implementation maxes out at ~2,300 messages per second, no matter if I use 200, 400 or 2000 Goroutines.
The program itself is very simple. For x Goroutines, it creates an SQS client, fetches messages from a queue, increments a counter, deletes the message from the queue. A separate Goroutine calculates the processes messages per second and prints it out.It's the very same behaviour (imho) with the Node.js program.
Any hints what I'm doing wrong?
Thanks!
[EDIT] Since people asked: We initially started having one SQS client defined in the main function and using this one in the Goroutines - doesn't matter, exact same results. Same for "creating an SQS client per Goroutine - no difference.
[EDIT 2] Since people asked: The Node.js lib being used does the message removal automatically.
[EDIT 3] As u/radekd pointed out, the sql-consumer lib for Node does a BatchDelete of the messages after it processed them. My initial Go code does not, it deletes each message individually. After changing the Go code to use DeleteMessageBatch, it's performing identical to the Node version, leaving me with the one thing I've already assumed: this is a network limited problem in general, nothing where Go could help me to improve performance BUT it's soothing to see, that it's performing at least as fast. It doesn't matter though, whether you define the SQS client in main or per worker. Same results.
GOPHERS: Go is not slower than Node! :-D
If anyone is interested, this is the Go code performing exactly as fast as the Node version for the exact same problem:
package main
import (
"context"
"fmt"
"log"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/sqs"
"github.com/aws/aws-sdk-go-v2/service/sqs/types"
"github.com/aws/aws-sdk-go/aws"
)
func main() {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
log.Fatalf("Unable to load SDK config, %v", err)
}
// Create an SQS client per worker with the default configuration
client := sqs.NewFromConfig(cfg)
queueUrl := "https://sqs.eu-central-1.amazonaws.com/0123456789/benchmark-queue"
receiveMessageInput := &sqs.ReceiveMessageInput{
QueueUrl: &queueUrl,
MaxNumberOfMessages: 10, // same as for the Node.js version
WaitTimeSeconds: 20, // Enable long polling like in Node.js sqs-consumer version - Benchmark: no difference regarding performance compared to short polling
}
var wg sync.WaitGroup
numGoroutines := 300
// Counter for the number of messages processed, to be incremented atomically
var messagesProcessed int64
// Start a separate goroutine to log processed messages every second
go func() {
for range time.Tick(time.Second) {
// Since multiple goroutines can update messagesProcessed, we retrieve the value atomically.
count := atomic.LoadInt64(&messagesProcessed)
fmt.Printf("Messages processed per second: %d\n", count)
// Reset the counter
atomic.StoreInt64(&messagesProcessed, 0)
}
}()
// Start multiple goroutines to process messages concurrently
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(workerId int) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", workerId)
// Receive messages in a loop until the channel is closed
for {
receiveMessageOutput, err := client.ReceiveMessage(context.TODO(), receiveMessageInput)
if err != nil {
fmt.Printf("Worker %d: Error receiving messages: %s\n", workerId, err)
continue
}
// If no messages are available, ReceiveMessage returns an empty slice
if len(receiveMessageOutput.Messages) == 0 {
fmt.Printf("Worker %d: Received no messages\n", workerId)
continue
}
// Create entries for batch deletion
var deleteEntries []types.DeleteMessageBatchRequestEntry
for id, message := range receiveMessageOutput.Messages {
// Create a new entry for each message
deleteEntries = append(deleteEntries, types.DeleteMessageBatchRequestEntry{
Id: aws.String(strconv.Itoa(id)),
ReceiptHandle: message.ReceiptHandle,
})
// Incrementing the counter
atomic.AddInt64(&messagesProcessed, 1)
}
// After processing the messages, delete them from the queue as a batch.
deleteBatchInput := &sqs.DeleteMessageBatchInput{
Entries: deleteEntries,
QueueUrl: &queueUrl,
}
_, err = client.DeleteMessageBatch(context.TODO(), deleteBatchInput)
if err != nil {
fmt.Printf("Worker %d: Failed to delete messages batch: %s\n", workerId, err)
}
}
}(i)
}
wg.Wait()
}This is the old code
package main
import (
"context"
"fmt"
"log"
"sync"
"sync/atomic"
"time"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/sqs"
)
func main() {
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
log.Fatalf("Unable to load SDK config, %v", err)
}
var wg sync.WaitGroup
numGoroutines := 200
// Counter for the number of messages processed, to be incremented atomically
var messagesProcessed int64
// Start a separate goroutine to log processed messages every second
go func() {
for range time.Tick(time.Second) {
// Since multiple goroutines can update messagesProcessed, we retrieve the value atomically.
count := atomic.LoadInt64(&messagesProcessed)
fmt.Printf("Messages processed per second: %d\n", count)
// Reset the counter
atomic.StoreInt64(&messagesProcessed, 0)
}
}()
// Start multiple goroutines to process messages concurrently
for i := 0; i < numGoroutines; i++ {
wg.Add(1)
go func(workerId int) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", workerId)
for {
client := sqs.NewFromConfig(cfg)
queueUrl := "https://sqs.eu-central-1.amazonaws.com/0123456789/benchmark-queue"
receiveMessageInput := &sqs.ReceiveMessageInput{
QueueUrl: &queueUrl,
MaxNumberOfMessages: 10, // same as for the Node.js version
WaitTimeSeconds: 20, // Enable long polling like in Node.js sqs-consumer version - Benchmark: no difference regarding performance compared to short polling
}
receiveMessageOutput, err := client.ReceiveMessage(context.TODO(), receiveMessageInput)
if err != nil {
fmt.Printf("Worker %d: Error receiving messages: %s\n", workerId, err)
continue
}
// If no messages are available, ReceiveMessage returns an empty slice
if len(receiveMessageOutput.Messages) == 0 {
fmt.Printf("Worker %d: Received no messages\n", workerId)
continue
}
for _, message := range receiveMessageOutput.Messages {
// Simulating message processing by incrementing the counter
atomic.AddInt64(&messagesProcessed, 1)
// After processing the message, delete it from the queue.
deleteInput := &sqs.DeleteMessageInput{
QueueUrl: &queueUrl,
ReceiptHandle: message.ReceiptHandle,
}
_, err := client.DeleteMessage(context.TODO(), deleteInput)
if err != nil {
fmt.Printf("Worker %d: Failed to delete message: %s\n", workerId, err)
}
}
}
}(i)
}
wg.Wait()
}In case you're interested, here's the Node.js version:
import { Consumer } from 'sqs-consumer'
const cluster = require('cluster')
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`)
// Total count of messages processed
let totalCount = 0
// Fork workers
for (let i = 0; i < 50; i++) {
cluster.fork()
}
// Function to handle message counts received from workers
function messageHandler(msg) {
if (msg.type === 'count') {
totalCount += msg.count
}
}
// Listen for messages from worker processes
for (const id in cluster.workers) {
cluster.workers[id].on('message', messageHandler)
}
// Log the total count every second and reset for the next interval
setInterval(() => {
console.log(`Messages per second: ${totalCount}`)
totalCount = 0
}, 1000)
} else {
let messageCount = 0
async function handleMessage(_snsMessage) {
messageCount++
}
const app = Consumer.create({
queueUrl: process.env.SQS_QUEUE_URL,
batchSize: 10,
handleMessageBatch: async (snsMessages) => {
const promises = []
for (const snsMessage of snsMessages) {
promises.push(handleMessage(snsMessage))
}
await Promise.all(promises)
},
handleMessage: async (snsMessage) => {
return await handleMessage(snsMessage)
},
})
// Send the message count to the master process every second, then reset to 0
setInterval(() => {
process.send({ type: 'count', count: messageCount })
messageCount = 0
}, 1000)
console.log('Starting SQS benchmark...')
app.start()
}Hey guys, I have a question that needs to hear your point of view. Most US jobs for back-end I see are hiring Nodejs. Although I love Golang because it's fast and resource usage efficient. But I think the main reason is people want to code and deliver their products faster and Node.js performance is enough for them.
Hope you guys give me more insight about it. Thanks!
I'm not a fan of any language but I know that there are some advantages and disadvantages of languages and framework alongside platforms matter - PHP is slow, yes it is slow comparing to any other languages but if you setup frakenPHP or swoole then is may compare with other languages -
I was just visiting dotnet page and saw that there is benchmark says 6x times faster than go and go even slower than nodejs
Why is it like this ? I'm not saying this is wrong and they are lying but, what is the explanation of this ? What does matter most such test cases?
Sources:
- https://imgur.com/a/Dx5B2kt
- https://dotnet.microsoft.com/en-us/
Is there anything that go does that node can't do?
Hi! I'm new to Golang and I'm trying it out.
I implemented the Kosaraju's algorithm in both Golang and node.js and measured the execution time. To my surprise, the Golang version is ~34% slower. I expected Go to be much faster than node.js.
I did my best to use the same logic and data structures for both languages to make sure I'm comparing apples to apples. I know that the algorithm itself can be improved, but I'm not looking for an algorithmic improvement, I just want to find out why Go is so much slower than node.js when both are executing the same thing.
I'll paste both scripts below. Each of them is a single file that you can run in the terminal. They'll execute the algorithm 6 times, then print the average time of the 6 executions. They expect a payload.json file in the same folder from which they'll load an adjacency matrix. I can't share my payload.json because it's too large (11 mb), but it's just a 2d matrix of random integers with 3000 rows and 3000 columns. Let me know if you need help generating it.
Can someone explain why is Go so slower than node?
graph.js
const fs = require('fs')
const { performance } = require('perf_hooks')
class Graph {
constructor() {
this._edges = new Map()
this._inNeighbors = new Map()
}
connect(vertexFrom, vertexTo, weight) {
if (!this._edges.has(vertexFrom)) {
this._edges.set(vertexFrom, new Map())
this._inNeighbors.set(vertexFrom, new Set())
}
if (!this._edges.has(vertexTo)) {
this._edges.set(vertexTo, new Map())
this._inNeighbors.set(vertexTo, new Set())
}
this._edges.get(vertexFrom).set(vertexTo, weight)
this._inNeighbors.get(vertexTo).add(vertexFrom)
}
findOutNeighbors(vertex) {
return new Set(this._edges.get(vertex).keys())
}
findInNeighbors(vertex) {
return new Set(this._inNeighbors.get(vertex))
}
findVertices() {
return new Set(this._edges.keys())
}
findConnectedComponents() {
const unvisited = this.findVertices()
const L = []
for (const vertex of this.findVertices()) {
this.visit(vertex, unvisited, L)
}
const components = new Map()
for (let i = L.length - 1; i >= 0; i -= 1) {
this.assign(L[i], L[i], components)
}
return components
}
visit(vertex, unvisited, L) {
if (unvisited.has(vertex)) {
unvisited.delete(vertex)
for (const neighbor of this.findOutNeighbors(vertex)) {
this.visit(neighbor, unvisited, L)
}
L.push(vertex)
}
}
assign(vertex, root, components) {
let hasBeenAssigned = false
for (const component of components.values()) {
if (component.has(vertex)) {
hasBeenAssigned = true
break
}
}
if (!hasBeenAssigned) {
if (!components.has(root)) {
components.set(root, new Set())
}
components.get(root).add(vertex)
for (const neighbor of this.findInNeighbors(vertex)) {
this.assign(neighbor, root, components)
}
}
}
}
function measureExecutionTime(f) {
const start = performance.now()
f()
return (performance.now() - start) / 1000
}
function buildGraphAndFindComponents(data) {
const graph = new Graph()
for (let i = 0; i < data.length; i += 1) {
for (let j = 0; j < data.length; j += 1) {
graph.connect(i, j, data[i][j])
}
}
return graph.findConnectedComponents()
}
function main() {
const numberExecutions = 6
const data = JSON.parse(fs.readFileSync('payload.json'))
// Run one time without measuring execution time to let the JIT compiler do
// its work
buildGraphAndFindComponents(data)
let totalElapsed = 0
for (let i = 0; i < numberExecutions; i += 1) {
const elaspedTime = measureExecutionTime(() => buildGraphAndFindComponents(data))
console.log(elaspedTime)
totalElapsed += elaspedTime
}
console.log('average: ' + (totalElapsed / numberExecutions).toFixed(2))
}
main()graph.go
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"time"
)
type Graph struct {
edges map[interface{}]map[interface{}]int
inNeighbors map[interface{}]map[interface{}]bool
expectedNodes int
}
func NewGraph(expectedNodes int) *Graph {
return &Graph{
edges: make(map[interface{}]map[interface{}]int, expectedNodes),
inNeighbors: make(map[interface{}]map[interface{}]bool, expectedNodes),
expectedNodes: expectedNodes,
}
}
func (graph *Graph) Connect(vertexFrom interface{}, vertexTo interface{}, weight int) {
if _, ok := graph.edges[vertexFrom]; !ok {
graph.edges[vertexFrom] = make(map[interface{}]int, graph.expectedNodes)
graph.inNeighbors[vertexFrom] = make(map[interface{}]bool, graph.expectedNodes)
}
if _, ok := graph.edges[vertexTo]; !ok {
graph.edges[vertexTo] = make(map[interface{}]int, graph.expectedNodes)
graph.inNeighbors[vertexTo] = make(map[interface{}]bool, graph.expectedNodes)
}
graph.edges[vertexFrom][vertexTo] = weight
graph.inNeighbors[vertexTo][vertexFrom] = true
}
func (graph *Graph) FindAllOutNeighbors(vertex interface{}) map[interface{}]bool {
keys := make(map[interface{}]bool, len(graph.edges))
for key := range graph.edges[vertex] {
keys[key] = true
}
return keys
}
func (graph *Graph) FindAllInNeighbors(vertex interface{}) map[interface{}]bool {
keys := make(map[interface{}]bool, len(graph.edges))
for key := range graph.inNeighbors[vertex] {
keys[key] = true
}
return keys
}
func (graph *Graph) FindAllVertices() map[interface{}]bool {
keys := make(map[interface{}]bool, len(graph.edges))
for key := range graph.edges {
keys[key] = true
}
return keys
}
func (graph *Graph) FindConnectedComponents() map[interface{}]map[interface{}]bool {
var visit func(interface{}, map[interface{}]bool, *[]interface{})
var assign func(interface{}, interface{}, map[interface{}]map[interface{}]bool)
visit = func(vertex interface{}, unvisited map[interface{}]bool, L *[]interface{}) {
if _, ok := unvisited[vertex]; ok {
delete(unvisited, vertex)
for neighbor := range graph.FindAllOutNeighbors(vertex) {
visit(neighbor, unvisited, L)
}
*L = append(*L, vertex)
}
}
assign = func(vertex interface{}, root interface{}, components map[interface{}]map[interface{}]bool) {
hasBeenAssigned := false
for root := range components {
if _, ok := components[root][vertex]; ok {
hasBeenAssigned = true
break
}
}
if !hasBeenAssigned {
if _, ok := components[root]; !ok {
components[root] = make(map[interface{}]bool)
}
components[root][vertex] = true
for neighbor := range graph.FindAllInNeighbors(vertex) {
assign(neighbor, root, components)
}
}
}
unvisited := graph.FindAllVertices()
L := make([]interface{}, 0, len(unvisited))
for vertex := range graph.FindAllVertices() {
visit(vertex, unvisited, &L)
}
components := make(map[interface{}]map[interface{}]bool)
for i := len(L) - 1; i >= 0; i -= 1 {
assign(L[i], L[i], components)
}
return components
}
func measureExecutionTime(f func()) time.Duration {
start := time.Now()
f()
return time.Since(start)
}
func buildGraphAndFindComponents(data [][]int) map[interface{}]map[interface{}]bool {
graph := NewGraph(len(data))
for i := range data {
for j := range data[i] {
graph.Connect(i, j, data[i][j])
}
}
return graph.FindConnectedComponents()
}
func main() {
const numberExecutions = 6
plan, _ := ioutil.ReadFile("./payload.json")
var data [][]int
err := json.Unmarshal(plan, &data)
if err == nil {
fmt.Errorf("Error: %w", err)
}
// Go doesn't have a JIT compiler, but let's do the same thing we did in JS
// anyway
buildGraphAndFindComponents(data)
var totalElapsed time.Duration
for i := 0; i < numberExecutions; i += 1 {
elaspedTime := measureExecutionTime(func() {
buildGraphAndFindComponents(data)
})
fmt.Println(elaspedTime)
totalElapsed += elaspedTime
}
fmt.Printf("average: %s\n", time.Duration(int64(totalElapsed)/int64(numberExecutions)))
}What do you think a person who wants to start learning web development should choose: JS (node.js) or Golang? It's been a couple of months since I started learning web development on the Odin project, but after studying the job market I found out that we have a very big competition for juniors on js, Golang has more offers (at the moment). P.s. I kinda hate CSS
Looking to build a web app and was wondering if go is the right choice here? I’m familiar with node and go syntactically but not as familiar with the advantages of each language at the core level.
I am not a professional developer but I have experience in express/node but reading through multiple posts, it seems like Go is really catching up in backend development. But i wonder if its better in web development!!!
Go also pays well as I have read many posts and people sharing their thoughts as well. But in a web based project where the apis are also used by mobile client, is it really worth it ditch node and move to Golang?
I already know that adding golang in my skillset is really nice but If I were to choose for a solid backend developer profile, what should I choose?
What is your opinions, guys, about learning golang?
I have experience with Node.js, Express, and nestjs
I will shift to Golang; I'm tired of the JS/TS ecosystem and I want to try something new!
http://ithare.com/five-myths-used-in-golang-vs-node-js-debate/
If SSR/Next.js/code sharing is not your context and if you are developing pure backend services (REST/Websocket/GraphQL anything) is there any reason to use Node.js/TS instead of GO in today's time?
So many companies are switching from Node/Python/PHP etc to GO and a constant barge of medium article praising how big performance improvement they got from moving to GO from Node etc.
The thing is, Node.js is only good for I/O but GO is good for both I/O and CPU intensive work so why feel restricted with just Node?. Moreover, if you are going to use Typescript (which you should) for static typing then why not use a proper statically typed language like GO where the types are enforced even at run time i.e. best of both worlds.
Basically with GO you get:
1] I/O and CPU bound capabilities, both (With node its just I/O and you have to be careful not to block eventloop)
2] Statically typed and runtime type safety as a result
3] No types mismatch where as with Node.js/TS it relies a lot on Deninately typed where types are separate from the actual code.
4] More performance at significantly less RAM usage. True multithreaded language
5] Low cloud bills and high scalability
6] Static binary as output so easy deployment using docker (or even without docker)
7] Big standard library so much less dependency on third party modules.
8] Functions are first class citizens so no heavy OOP design patterns like Java/C# etc.
9] Very simple language and easy to learn in a short time. So Typescript developers can learn GO quickly and be productive within weeks.
With so many advantages as above, is there any reason to develop a pure backend server in Node.js/TS compared to GO?
I have spent 2 months learning and building nodejs backend and around an year in frontend. Now I want to dive deeper into backend. So should I migrate to Golang or stick with nodejs. The end goal is to become a great irreplaceable developer.
I've been advocating Go to a friend who's in a position to make this choice. I googled for "Go vs Node.js" articles, but there's surprisingly little on the web comparing the two (and what's there is pretty bad and uses bubble sort as a basis for comparison).
Basically, unless you're going to get a lot of value out of being able to reuse code on the client and the server, I just don't see any reason to choose Node.js over Go for the types of things you'd use Node.js for.
That's how it appears to me, but I'm looking for reasonable counterarguments so I can help my friend make an informed decision that's not just based on my biases. Thanks!