
Crystal lang vs NodeJS vs Golang http benchmark
I like performance and efficiency. That's why I benchmark regularly code, frameworks etc... NodeJS is based on V8, the javascript engine of Chrome, developed by Google, which is fast for an interpreted environment. Crystal is a language that is inspired by Ruby, but compiles into a static binary file, with LLVM. Go is a static language created by Google, inspired by C and Pascal.
Test configuration
This is the configuration where tests will run :
- i7-4702MQ - 4 cores, 8 threads
- 8Gb SO-DIMM DDR3L
We will test a simple http server with 1 thread, and with 4 threads. Versions of softwares used :
- Nodejs : v7.2.1
- Crystal : v0.20.0
- Go : v1.7.4
Results
Mono Threaded
- NodeJS mono-threaded :
node --use-strict mono.js
wrk -t3 -c1000 -d10s http://127.0.0.1:8080
Running 10s test @ http://127.0.0.1:8080
3 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 48.31ms 147.83ms 1.69s 96.97%
Req/Sec 10.58k 2.41k 20.70k 74.67%
315881 requests in 10.01s, 38.86MB read
Socket errors: connect 0, read 220, write 0, timeout 0
Requests/sec: 31558.31
Transfer/sec: 3.88MB
- Crystal-lang mono-threaded :
crystal build --release mono.cr && ./mono
wrk -t3 -c1000 -d10s http://127.0.0.1:8080
Running 10s test @ http://127.0.0.1:8080
3 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 11.90ms 27.66ms 836.66ms 99.08%
Req/Sec 32.59k 2.65k 41.07k 67.33%
972623 requests in 10.00s, 85.34MB read
Requests/sec: 97222.67
Transfer/sec: 8.53MB
- Golang mono-threaded :
go build -ldflags "-s -w" mono.go && ./mono
wrk -t3 -c1000 -d10s http://127.0.0.1:8080
Running 10s test @ http://127.0.0.1:8080
3 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 13.39ms 8.54ms 219.59ms 94.19%
Req/Sec 23.76k 2.16k 29.46k 75.33%
709427 requests in 10.00s, 96.07MB read
Requests/sec: 70913.89
Transfer/sec: 9.60MB
Multithreaded
- NodeJS multithreaded :
node --use-strict multi.js
wrk -t3 -c1000 -d10s http://127.0.0.1:8080
Running 10s test @ http://127.0.0.1:8080
3 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 24.02ms 93.68ms 1.28s 96.96%
Req/Sec 31.83k 4.22k 45.84k 89.00%
950461 requests in 10.01s, 116.93MB read
Requests/sec: 94996.08
Transfer/sec: 11.69MB
- Crystal-lang multithreaded :
crystal build --release multi.cr && ./multi
wrk -t3 -c1000 -d10s http://127.0.0.1:8080
Running 10s test @ http://127.0.0.1:8080
3 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 3.10ms 827.26us 21.19ms 72.01%
Req/Sec 107.64k 6.10k 120.57k 91.00%
3212392 requests in 10.01s, 281.85MB read
Requests/sec: 320763.86
Transfer/sec: 28.14MB
- Golang multithreaded :
Golang doesn't expose SO_REUSEPORT yet, so we can't really test it with multithreading
Update :
As mentionned by Sergey Lanzman, Golang > 1.5 user all cores, so I dir another benchmark with version 1.7.4 on all cores (> 4):
wrk -t3 -c10000 -d10s http://127.0.0.1:8080
Running 10s test @ http://127.0.0.1:8080
3 threads and 10000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 53.31ms 125.26ms 1.83s 97.17%
Req/Sec 52.53k 17.03k 100.42k 63.12%
1557873 requests in 10.05s, 210.97MB read
Socket errors: connect 0, read 340, write 0, timeout 319
Requests/sec: 154981.46
Update 2 :
Damon Zhao has mentionned the package fasthttp, here is the result with this package :
wrk -t3 -c1000 -d10s http://127.0.0.1:8080
Running 10s test @ http://127.0.0.1:8080
3 threads and 1000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 3.22ms 5.17ms 203.04ms 99.81%
Req/Sec 106.02k 5.75k 124.38k 75.33%
3162268 requests in 10.01s, 533.79MB read
Requests/sec: 315919.73
Transfer/sec: 53.33MB
Conclusions
Crystal is fast, faster than go and 3x than nodejs, and scales very well. NodeJS is the slower, but it could be multithreaded, so it can handle more requests thant Go. Go doesn't use SO_REUSEPORT, so we can't realy test it with multithreading. Even with all cores, Golang is not as fast as Crystal, but faster than NodeJS. The 3 languages could be used for net/web applications. Crystal is inspired by Ruby. If you like Ruby and want performances, you should try Crystal.
Update :
Go package fasthttp is as fast as Crystal default http module. You need to install the fast http package to use it : go get -u github.com/valyala/fasthttp
. If we only consider the default modules, Crystal wins.
The code
NodeJS mono-threaded code
var http = require('http');
const PORT=8080;
function handleRequest(req, res)
{
res.end('Welcome on Hackmyfortress.com');
}
var server = http.createServer(handleRequest);
server.listen(PORT, function()
{
console.log("Server listening on: http://localhost:%s", PORT);
});
NodeJS multi-threaded code
var cluster = require("cluster");
var http = require("http");
const PORT=8080;
var NUM = 1;
var numThread = 4;
if (cluster.isMaster)
{
var i = 0;
while (i < numThread)
{
cluster.fork();
i++;
}
cluster.on("exit", function(worker, code, signal)
{
console.log("Worker " + worker.process.pid + " died");
});
}
else
{
function handleRequest(req, res)
{
res.end('Welcome on Hackmyfortress.com');
}
var server = http.createServer(handleRequest);
server.listen(PORT, function()
{
console.log("Server listening on: http://localhost:%s", PORT);
});
}
Crystal lang mono-threaded code
require "http/server"
server = HTTP::Server.new(8080) do |context|
context.response.print "Welcome on Hackmyfortress.com"
end
puts "Listening on http://127.0.0.1:8080"
server.listen
Crystal lang multi-threaded code
SO_REUSEPORT must be added to crystal to enable multi-threading capacities.
require "http/server"
class Cluster
def self.fork (env : Hash)
env["FORKED"] = "1"
return Process.fork { Process.run(PROGRAM_NAME, nil, env, true, false, true, true, true, nil ) }
end
def self.isMaster
(ENV["FORKED"] ||= "0") == "0"
end
def self.isSlave
(ENV["FORKED"] ||= "0") == "1"
end
def self.getEnv (env : String)
ENV[env] ||= ""
end
end
numThread = 4;
if(Cluster.isMaster())
numThread.times do |i|
Cluster.fork({"i" => i.to_s})
end
sleep
else
server = HTTP::Server.new(8080) do |context|
context.response.print "Welcome on Hackmyfortress.com"
end
puts "Listening on http://127.0.0.1:8080"
server.listen
end
Golang mono-threaded code
package main
import (
"net/http"
)
type Hello struct{}
func (h *Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Welcome on Hackmyfortress"));
}
func main() {
http.ListenAndServe(":8080", &Hello{})
}
Golang multi-threaded code
Unfortunately, Golang doesn't include SO_REUSEPORT yet, and goroutines aren't thread, so, no result yet for multithreaded Go example.
Update :
As mentionned by Sergey Lanzman, no need to use SO_REUSEPORT in Golang > 1.5, GOMAXPROCS is set to all. So the same code in "Monothreaded" section is used here.
Update 2 :
Damon Zhao has mentionned the package fasthttp for Golang, here is the code used with fasthttp :
package main
import(
"log"
"fmt"
"github.com/valyala/fasthttp"
)
func main()
{
requestHandler := func(ctx *fasthttp.RequestCtx)
{
fmt.Fprintf(ctx, "Welcome on Hackmyfortress.com")
}
s := &fasthttp.Server
{
Handler: requestHandler,
Name: "Hackmyfortress Server",
}
if err := s.ListenAndServe("127.0.0.1:8080"); err != nil
{
log.Fatalf("error in ListenAndServe: %s", err)
}
}
Adrien