Recently, I’ve read quite a bit of comparisons of Node.js with Golang. Unfortunately, Google search on this subject is dominated by, ahem, not so informed opinions – with lots of outright ridiculous claims; and while repeating ridiculous claim over and over doesn’t make it less ridiculous – it certainly can form a completely wrong opinion among developers, and even worse – among decision-makers. To help dealing with it, I made a collection of the most popular/most ridiculous myths about Node and Golang – to bust them here.
Myth #1: Node.js is single-threaded, so it doesn’t scale
if programmer does want to go multicore, all she has to do is to use goroutines (in Golang) , or cluster module (in Node)
Probably the most popular deadly wrong point touted by competitors of Node.js is that, as Sathananthan1 proudly writes, “Node.js is single-threaded, which means that the execution instructions are performed in sequence.”.
Truth #1: If programmer does nothing to exploit multi-coring in her program, both golang and Node.js are single-threaded. However, if programmer does want to go multicore, she has to use goroutines (in Golang) , or cluster module (in Node). All one might want to argue that golang’s approach is better – however, this is at least arguable (more on it in Myth #3 and Myth #4 below).
1 an article which got #1 placement and Google “feature snippet” on “Node.js vs golang” search, is an article by an undisputed authority in the field a guy writing his 3rd-semester project. Bravo, Google!</sarcasm>
Myth #2: Node.js is interpreted
Numerous sources discuss allegedly interpreted nature of JavaScript; see, for example, Harkushko: “But since JavaScript is an interpreted language, it takes more time to execute code written in Node.js than it does to execute code written in Go.”
Truth #2: While performance of Node.js is indeed usually lower than that of Go, it has nothing to do with the language being interpreted (JavaScript as a specification doesn’t need to be interpreted or compiled; it is particular implementation which is interpreted or compiled, and Node.js v8 engine does have a JIT compiler). This is corroborated by an observation that performance difference between the two is relatively small (see Myth #5 below).
Myth #3: Goroutines is The way to scale
mutex-based stuff is inherently untestable
It is very often mentioned that Golang is designed for scalability; just as one example, the very same ultra-high-rated-by-Google Sathananthan says “Golang was really designed for scalability and concurrency, without too much hassle.”. This usually comes along with some wording about goroutines-based concurrency, at least implying that scalability can/should be achieved with goroutines.
Truth #3: In fact, goroutines have a potential Really Big Problem(tm) when it comes to scalability – as goroutines DO allow accessing non-const global variables, this not only opens the whole can of worms reliability-wise (heck, mutex-based stuff is inherently untestable!2), but also limits scalability to one single box (moreover, Shared-Memory systems DO suck scalability-wise even on single box; this has lots of reasons ranging from Amdahl’s law to unnecessary cache invalidations due to mutex-induced thread context switches). Granted, current golang practices DO encourage channel-based Shared-Nothing concurrency – which indeed DOES scale, but to the best of my knowledge, (a) it is NOT enforceable in Go, and (b) golang run-time (unlike, say, Erlang/OTP runtime) doesn’t support/detect “pure” nonconst-global-access-free goroutines (which could be moved even to another box if necessary). BTW, Node.js is Shared-Nothing by design – enforcing the only scalable model out there (and also saving us from LOTS of next-to-impossible-to-find bugs as a side benefit).
2 and Go’s runtime race detection provides only a palliative care to this Big Fat Problem(tm)
Myth #4: Concurrency in Golang is easy
Cheney says “Go’s headline feature is our simple, lightweight concurrency model. As a product, our language almost sells itself on this on feature alone.”.
concurrency (leave alone efficient concurrency) is never easy
Truth #4: Golang or not, concurrency (leave alone efficient concurrency) is never easy. To illustrate it, let’s take a look at an excellent article Deleplace on an optimization experience with Golang. Long story short – first, supposedly “good”3 Golang code was made 2.5x faster by removing fine-grain concurrency and making the whole thing sequential(!!). Then, when adding a coarse-grain concurrency, wall-clock time did improve further, but number of goroutines going into 120K proved to be too much for Go runtime (causing 12 cores to perform merely 1.5x better than single-core version – that’s about 8x inefficiency CPU-wise, energy-wise, and CO2-footprint-wise)4, so final implementation (the most efficient one, gaining whopping 23x improvement over original “good” code) was based on 12 workers. But hey – not only this kind concurrency optimization struggle is very similar regardless of the programming language, in addition I cannot help not to mention that 12-workers for 12-cores would be the way we’d scale our Node.js implementation in the first place! In other words –
efficient concurrent implementations happen to be strikingly similar in Golang and Node.js
There is no magic out there – and while costs of goroutines are somewhat lower than that of threads, common techniques such as going coarse-grain and limiting the number of outstanding tasks are still necessary.
3 at least some Golang developer wasn’t shy to put it to the Github as an explicit example of a “good” code
4 effectively busting yet another myth of Golang being ok with millions of goroutines
Myth #5: Golang performance is similar to that of C/C++
Sathananthan says that Golang has “Similar performance characteristics as with C or C++”.
Truth #5: if we take a look at IMO-the-best-inter-language-benchmark-results-available (Debian2), we’ll see that Go lost all 10 benchmarks to GCC C, losing from 20% to 5x (with a median around 2.5x). Moreover, if speaking about performance of Node.js (Debian3), it happens to be between 20% faster and 5.9x slower than Golang (with a median of 1.4x). In other words,
Performance-wise, Golang is closer to Node.js than to C/C++
Bottom Line
I do NOT feel that either Node.js or Golang is 'better' per se
Myth busting aside, I do NOT feel that either Node.js or Golang is “better” per se; in a sense, they’re strikingly similar (especially if speaking about Go with channels instead of mutexes), so all the attempts to push one of them as an “inherently better ” one are pretty much pointless. The only field where Golang has a realistic edge, is that 1.4x performance advantage, but this is going to change with an advent of Node.cpp, which will enable C++-like performance for those-Nodes-which-need-it.
References
[Sathananthan] Sabesan Sathananthan, "Want to be a best Web Developer? Learn Golang not Node.js"
[Harkushko] Liliia Harkushko, "https://yalantis.com/blog/golang-vs-nodejs-comparison/"
[Deleplace] Val Deleplace, "Go code refactoring : the 23x performance hunt"
[Debian1] "Go versus C++ g++ fastest programs"
[Debian2] "C gcc versus C++ g++ fastest programs"
[Debian3] "Node js versus C++ g++ fastest programs"
[Concurrency made easy] Dave Cheney, "https://dave.cheney.net/paste/concurrency-made-easy.pdf"







Comments