Skip to main content

To boldly Go where Node man has gone before

With all the chatter about how uber-amazing Node.js is I figured I'd do a little comparison with my favorite language du jour: Go.  Node's claim is that it's "a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications."

So, easy to build; fast; scalable.

Here's the canonical Node program for Hello, World from the Node home page.

var http = require('http');
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n');
}).listen(1337, '127.0.0.1');

console.log('Server running at http://127.0.0.1:1337/');

And here's the equivalent program written in Go. It's a little longer because Go insists on explicitly importing the things you use and has a little more boilerplate (such as having a func main()).
package main

import (
 "net/http"
 "log"
 "fmt"
)

func main() {
 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  w.Header().Set("Content-Type", "text/plain")
  fmt.Fprintf(w, "Hello, World\r\n")
 })

 log.Printf("Server running at http://127.0.0.1:1337/")
 http.ListenAndServe("127.0.0.1:1337", nil)
}

So, in terms of 'easy to build' there's no clear winner. Node is a little more compact, but the core functionality is the same: start a server and do a callback when a connection is made.

So, then there's 'fast' and 'scalable'.  To test those I used ab on Ubuntu on a MacBook Pro with 8GB of RAM.  Here are the results.

First test was ab -n 1000000 (i.e. 1,000,000 requests):

LanguageElapsed time (seconds)Requests/secondms per requestTransfer rate (KBps)Peak real memory (KB)Peak virtual memory (KB)
Go137.5427270.510.138681.614,120145,308
Node200.3414989.260.200370.3049,258638,700

The second test was ab -n 1000000 -c 100 (i.e. 1,000,000 requests with 100 simultaneously)

LanguageElapsed time (seconds)Requests/secondms per requestTransfer rate (KBps)Peak real memory (KB)Peak virtual memory (KB)
Go141.8247051.020.142661.0021,684902,884
Node177.4725634.680.177418.2050,724643,912

So, Node was always slower than Go and (almost always) used more memory.   The only time Go was 'worse' than Node was in virtual memory usage in the second test.

I'm unimpressed by Node.  Go's approach (here it is spawning a goroutine per connection) is much simpler from a programming perspective and more performant.  The code handling the connection doesn't have to be concerned about blocking/non-blocking calls or whether something is asynchronous.  You just write the code to handle that particular URL.

PS I should add that I did these tests in a Ubuntu VM which was restricted to running on a single processor core.  That was done so that any advantage Go would get because it can inherently use multiple cores would be eliminated.  Bottom line is that Go is faster, and easy to write.

PS People have asked what happens with more simultaneous connections.  Here are some graphs showing the real and virtual memory use and the requests per second for Go and Node.   Go uses less real memory and serves more requests per second at 0, 100, 500 and 1,000 simultaneous requests, but Go's virtual memory grows.




Comments

Anonymous said…
Isn't the Go version using all CPUs whereas the Node version is using just one? Would be nice to test with a version using Cluster.
Leon said…
node sucks
Leon said…
node sucks
brad clawsie said…
well one of the strengths of Go is that you don't need things like Cluster to exploit modern hardware. Go has it baked-in.
brad clawsie said…
This comment has been removed by the author.
brad clawsie said…
This comment has been removed by the author.
The Go version is doing HTTP chunking and sending a Date header, etc.

The Node version is sending a Content-Type and omitted a Date header (IIRC).

I think there a few other differences too. I haven't looked into this particular microbenchmark in awhile.

I seem to recall Go doing even better once you made them do the same work.
The Go version is doing HTTP chunking and sending a Date header, etc.

The Node version is sending a Content-Type and omitted a Date header (IIRC).

I think there a few other differences too. I haven't looked into this particular microbenchmark in awhile.

I seem to recall Go doing even better once you made them do the same work.
The Go version is doing HTTP chunking and sending a Date header, etc.

The Node version is sending a Content-Type and omitted a Date header (IIRC).

I think there a few other differences too. I haven't looked into this particular microbenchmark in awhile.

I seem to recall Go doing even better once you made them do the same work.
@Brad.

When node is running:

* About to connect() to 127.0.0.1 port 1337 (#0)
* Trying 127.0.0.1... connected
> GET / HTTP/1.1
> User-Agent: curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
> Host: 127.0.0.1:1337
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Connection: keep-alive
< Transfer-Encoding: chunked
<
Hello World
* Connection #0 to host 127.0.0.1 left intact
* Closing connection #0

When Go is used:

curl -v http://127.0.0.1:1337/
* About to connect() to 127.0.0.1 port 1337 (#0)
* Trying 127.0.0.1... connected
> GET / HTTP/1.1
> User-Agent: curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
> Host: 127.0.0.1:1337
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Wed, 16 May 2012 18:55:12 GMT
< Transfer-Encoding: chunked
<
Hello, World
* Connection #0 to host 127.0.0.1 left intact
* Closing connection #0

Node isn't sending the Date header, but it is sending Connection header.
pankaj said…
From these numbers, Node seems to perform better when under load. On the other, Go's resource consumption spikes up across all metrics.

Can you share why this is the case for Go?
pankaj said…
Go's resource consumption spikes up across all metrics in your second test of 100 concurrent requests. Node is handling this load much more gracefully.
I am new to Go...any idea why this behavior under load ?
Anonymous said…
I don't see why this left you unimpressed with Node. Personally I'm impressed it performed so closely to a compiled language. Add in the simplicity of a dynamic language with string manipulation, regular expressions, etc and it's a big win for most coders.
Anonymous said…
I don't see why this left you unimpressed with Node. Personally I'm impressed it performed so closely to a compiled language. Add in the simplicity of a dynamic language with string manipulation, regular expressions, etc and it's a big win for most coders.
Unknown said…
Don't forget you can write your Node code in CoffeeScript which has great syntax and is fantastically literate, and that you gain the advantage of all the server-side JS libraries that are popping up.

Of course 'real' (thousands of users, etc) applications should probably be written in a compiled language, but for toy, prototype, or specialty applications (I've written small web applications that only 10 people will ever use and probably not even concurrently) Node has its place.
Carmelo said…
Similarly to @pankaj

While Node.js increases its performance in the second test, Go makes things worse :-)

Would be nice to try with more connections in parallel and then creating some graphs.

You cannot perform just 2 experiments to state something boldly :-)
Holger said…
I recently watched a presentation about Haskell and during that a graph was put up which claimed that for a trivial benchmark like this, go really did outperform go, but all three Haskell libs tested outperformed go as well. I.e. using all cores is good but using all cores more efficiently is even better.
Mina Naguib said…
Along the single-core lines, here's a brief C version with error handling omitted for brevity: http://pastebin.com/90L5SFkg

Base memory: RSS 472K VSZ 17.4MB
Peak memory: RSS 984K VSZ 17.4MB

AB results:
Concurrency 1: 13003reqs/s
Concurrency 10: 28285reqs/s
Concurrency 100: 26872reqs/s
Log: http://pastebin.com/nM0Enke8

This is on an older Macbook Pro than the one you used for your Node & Go tests.
TechShaman said…
This comment has been removed by the author.
TechShaman said…
Add a couple more lines to make the node version a cluster:
https://gist.github.com/2722169

Then run the tests.

Here are my findings, which show that node isn't that bad, in fact it consistently outperformed go in all tests:

https://gist.github.com/2722254


Of course, results from a hello world test are hardly an indication of how a platform performs in the real world.

Go looks like a promising language, I would definitely like to learn it.
I'm sure it's a powerful tool, knowing that Google is behind it and I assume it performs well in production.

I've been very satisfied with node so far, not just for it's speed, but for the community and collection of open libraries out there.

I've never used it in production, though, but it's potential as a programming tool is great.

It is a handy little tool which gets a lot of things done fast.

Combined with coffee script, this is the fastest way to prototype almost any kind of (non-gui) app.
kyb said…
How is the go code configuring the handler for the server that's instantiated later? It looks like it's doing it with a global static which would immediately make me want to avoid it at all costs.

Popular posts from this blog

Your last name contains invalid characters

My last name is "Graham-Cumming". But here's a typical form response when I enter it:


Does the web site have any idea how rude it is to claim that my last name contains invalid characters? Clearly not. What they actually meant is: our web site will not accept that hyphen in your last name. But do they say that? No, of course not. They decide to shove in my face the claim that there's something wrong with my name.

There's nothing wrong with my name, just as there's nothing wrong with someone whose first name is Jean-Marie, or someone whose last name is O'Reilly.

What is wrong is that way this is being handled. If the system can't cope with non-letters and spaces it needs to say that. How about the following error message:

Our system is unable to process last names that contain non-letters, please replace them with spaces.

Don't blame me for having a last name that your system doesn't like, whose fault is that? Saying "Your last name …

All the symmetrical watch faces (and code to generate them)

If you ever look at pictures of clocks and watches in advertising they are set to roughly 10:10 which is meant to be the most attractive (smiling!) position for the hands. They are actually set to 10:09.14 if the hands are truly symmetrical. CC BY 2.0image by Shinji
I wanted to know what all the possible symmetrical watch faces are and so I wrote some code using Processing. Here's the output (there's one watch face missing, 00:00 or 12:00, because it's very boring):



The key to writing this is to figure out the relationship between the hour and minute hands when the watch face is symmetrical. In an hour the minute hand moves through 360° and the hour hand moves through 30° (12 hours are shown on the watch face and 360/12 = 30).
The core loop inside the program is this:   for (int h = 0; h <= 12; h++) {
    float m = (360-30*float(h))*2/13;
    int s = round(60*(m-floor(m)));
    int col = h%6;
    int row = floor(h/6);
    draw_clock((r+f)*(2*col+1), (r+f)*(row*2+1), r, h, floor(m…

The Elevator Button Problem

User interface design is hard. It's hard because people perceive apparently simple things very differently. For example, take a look at this interface to an elevator:


From flickr

Now imagine the following situation. You are on the third floor of this building and you wish to go to the tenth. The elevator is on the fifth floor and there's an indicator that tells you where it is. Which button do you press?

Most people probably say: "press up" since they want to go up. Not long ago I watched someone do the opposite and questioned them about their behavior. They said: "well the elevator is on the fifth floor and I am on the third, so I want it to come down to me".

Much can be learnt about the design of user interfaces by considering this, apparently, simple interface. If you think about the elevator button problem you'll find that something so simple has hidden depths. How do people learn about elevator calling? What's the right amount of informati…