You can join the discussion on HackerNews here.
It’s a follow up post to the previous Ruby vs Crystal Performance.
I guess this time it will be a fair performance comparison as both languages are compiled and statically typed.
We will perform a couple of tests:
- Finding a number in the Fibonacci sequence as in the previous post
- Running an HTTP server locally and performing benchmarks with wrk
Language versions installed my machine are:
- go version go1.14.3 darwin/amd64
- Crystal 0.34.0 (2020-04-07)
I’m curious to find out how Go and Crystal perform in comparison to each other.
Compilation
For the tests we will be running previously compiled programs. We will use the release flag to enable optimizations in Crystal:
crystal build --release program.cr
Go binaries don’t have a release version and we won’t be using any flags. So, it’s just:
go build program.go
Fibonacci
Alright, first we will write code to generate a Fibonacci sequence for a given number. Let’s find the 47th number which is 2,971,215,073.
Go version:
package main
import "fmt"
func fibonacci(n uint32) uint32 {
if n < 2 {
return n
}
return fibonacci(n-1) + fibonacci(n-2)
}
func main() {
fmt.Println(fibonacci(47))
}
Crystal version:
def fibonacci(n : UInt32)
return n if n < 2
fibonacci(n-1) + fibonacci(n-2)
end
puts fibonacci(47)
Results on my machine (MacBook Pro 2.2 GHz Intel Core i7):
Language | Binary size | Run time | Memory usage |
go | 2.1M | 21.28s | 2.01M |
Crystal | 418k | 19.69s | 1.72M |
Crystal is slightly winning here.
A few observations here:
Crystal’s binary size is 5 times smaller than Go’s. Though, they can be slightly reduced in size when we omit the debug information:
go build -ldflags="-w" fibonacci_golang.go
This way the binary size goes down from 2.1M to 1.7M.
Also, not in this particular example, but generally Go’s compilation time is much much faster than Crystal’s.
HTTP Server
Now, let’s create a simple HTTP server using standard libraries. Both Go’s net/http and Crystal’s http/server employ concurrency: Go uses goroutines and Crystal uses fibers.
Go version:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", HelloServer)
http.ListenAndServe(":8080", nil)
}
func HelloServer(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello from %s!", r.URL.Path[1:])
}
Crystal version:
require "http/server"
server = HTTP::Server.new do |context|
context.response.content_type = "text/plain"
context.response.print "Hello from #{context.request.path}!"
end
puts "Listening on http://127.0.0.1:8080"
server.listen(8080)
For benchmarking we will be using wrk. If you’re not familiar with this tool it’s like a pretty well known ApacheBench (ab) but a modern version.
Here is how we can run a benchmark for 60 seconds, using 8 threads, and keeping 400 HTTP connections open:
wrk -t8 -c400 -d60s http://localhost:8080/hello
Results for the Go server:
Running 1m test @ http://localhost:8080/hello
8 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 3.56ms 2.26ms 95.31ms 92.00%
Req/Sec 8.77k 2.24k 15.75k 64.66%
4190457 requests in 1.00m, 535.51MB read
Socket errors: connect 157, read 100, write 0, timeout 0
Requests/sec: 69757.81
Transfer/sec: 8.91MB
Results for the Crystal server:
Running 1m test @ http://localhost:8080/hello
8 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 2.89ms 0.97ms 19.01ms 80.34%
Req/Sec 10.54k 3.41k 18.14k 60.85%
5035284 requests in 1.00m, 513.82MB read
Socket errors: connect 157, read 85, write 0, timeout 0
Requests/sec: 83917.26
Transfer/sec: 8.56MB
Results:
Language | Binary size | Memory usage | CPU usage | Throughput |
go | 7.4M | 20.2M | 300% | 69,757 |
Crystal | 966kb | 19.1M | 99% | 83,917 |
Crystal again shows better results.
CPU utilization over 100% in the table might seem confusing. But it simply means the system uses multiple cores. One core at max is 100%.
My machine has 8 cores as it can be seen with the following command on macOs:
sysctl -n hw.ncpu
Conclusion
Frankly speaking, we have only performed a couple of small tests to make any conclusions but I’m still excited for Crystal as a young language but showing great results.