Five Myths used in “Golang vs Node.js” debate

 
Author:  Follow: TwitterFacebook
Job Title:Sarcastic Architect
Hobbies:Thinking Aloud, Arguing with Managers, Annoying HRs,
Calling a Spade a Spade, Keeping Tongue in Cheek
 
 

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

Hare thumb up: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 [Sathananthan]1 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

Hare wondering if you are crazy:mutex-based stuff is inherently untestableIt 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

Judging hare:concurrency (leave alone efficient concurrency) is never 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.”.

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 ([Debian1][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

Surprised hare:I do NOT feel that either Node.js or Golang is 'better' per seMyth 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.

 

Don't like this post? Comment↯ below. You do?! Please share: ...on LinkedIn...on Reddit...on Twitter...on Facebook

Acknowledgement

Cartoons by Sergey GordeevIRL from Gordeev Animation Graphics, Prague.

Join our mailing list:

Comments

    • "No Bugs" Hare says

      I’d agree – but as now Node gets Typescript (with classes – which Golang is still lacking), the comparison becomes less obvious.

  1. says

    Go brings some nice things that Nodejs lacks as far as I am aware. Some crossing my mind:
    – fast cross compilation cpu arch and os wise to static binaries
    – easily have no C/C++ dependencies and everything written in Go or assembly only
    – Go tests, language server and linter seem to run slightly quicker than node ones in my experience.
    – No huge node_modules required to run in production, just a ~10MB static binary

    Then Nodejs with Typescript is quite nice especially for running tasks involving Javascript such as server side rendering for example, where Go might not be suitable. And there are many more Javascript developers than Go developers too.

    Anyway, thanks for the great article!

    • "No Bugs" Hare says

      Sure, thanks! The point of this article was NOT to compare Node.js to Golang; the point was to exclude quite a bit of popular BS (which Google pushes to the top of “node.js vs golang” searches), so a fair comparison can be made.

      That being said, I have to note that for any major OS it is next to impossible to “have no C/C++ dependencies” (system calls are 100% C for {Linux,Win32,Mac OS, Solaris, AIX, …}, so at least _some_ C code will be normally required to call system-level stuff – well, unless Go implements C ABIs but this would be a long stretch).

  2. a-square says

    Go has this great green thread engine that makes it very easy to write concurrent code (I don’t mean write efficient concurrent code, I mean write concurrent code at all). In Node.js, you have to use closures for this, which is a more difficult programming model to work with. So if one’s writing a service that queries a couple more services or data sources, tosses JSONs around and returns a result, Go is a better choice IMO, especially for inexperienced developers.

    What do you think about Deno BTW?

    • "No Bugs" Hare says

      ” In Node.js, you have to use closures for this,” – or in recent Node, we can (and IMO SHOULD) use await, which is IMNSHO a FAR superior model compared to BOTH lambdas and goroutines (see http://ithare.com/eight-ways-to-handle-non-blocking-returns-in-message-passing-programs-with-script/ ), especially if we do need to work with stateful Nodes (and being stateful is in turn a prerequisite if we want to avoid pushing all the changes onto DB, which does not scale at all – see, for example, Uber’s problems with migrating from MySQL to Postgre and back – though, as we discussed with late Herbert Matthews, the whole thing could have been solved by in-memory state spending orders of magnitude less efforts).

      Bottom line – we COULD argue about goroutines-vs-lambdas, but argument about goroutines-vs-await doesn’t have any realistic ground.

      As for Deno – feelings are mixed:
      CON: Rust is overhyped – both safety-wise and performance-wise (my own C++ will run in circles around it – well, it runs in circles even around Node’s C-based libuv 😉 );
      PRO: Typescript is good
      PRO: permission system looks ok
      CON: loading modules from URLs? Seriously? In production? Gimme a break!

  3. Roman Leventov says

    A major consideration is ecosystem maturity (libraries and tools). I don’t have experience with Go, but moving from Java to Node, I was depressed by the state and the quality of libraries (the majority of them are practically not supported at all, publish-once code) and tools (IDE, build tools)

  4. Pseudonym says

    If Alex Stepanov were he, he would point out that Java, Go, and Node.js have similarly fast performance, but only if your program is valid Fortran.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.