Measuring Golang and Echo against Kotlin and Vert.x
Intro
Software bench-marking is extremely tough but it is something I really enjoy doing. Whether that is running apache bench on HTTP servers, running redis-benchmark, or pgbench - it is always interesting seeing how various tweaks impact performance.
Two of my go to languages for building anything are Kotlin and Golang. In those, two of my go to libraries to build HTTP services are Vert.x and Echo respectively. It was natural instinct to see how they perform when put under a stress test.
The internet is filled with comments around the JVM being slower compared to native code - It is, while the code gets JIT’d but after that it should perform at a similar level as native code.
App code
Let’s look at the code for the two applications.
1 | fun main() { |
1 | func main() { |
I am using Openj9 JVM on JDK 13.
1 | $ java -version |
Both languages start a server on their respective port and return a 200 on /
with an empty string response. Something to keep in mind is that the vert.x http server is single threaded where as the echo server is not.
Curl timing
The first thing I wanted to see was a breakdown of the time it takes to do a single request. We can see the breakdown in curl by using the following file
1 | # curl-format.txt |
As expected the first request to the JVM application was quite slow coming in at around 180ms
1 | $ curl -w "@curl-format.txt" -o /dev/null -s http://localhost:9999/ |
The first request to the Go server was already quite optimised and responded in under 5ms
1 | $ curl -w "@curl-format.txt" -o /dev/null -s http://localhost:1323/ |
The JVM does optimisations based on the traffic it sees, which became evident as I started making a few more requests.
1 | $ curl -w "@curl-format.txt" -o /dev/null -s http://localhost:9999/ |
The response times fell from 180ms down to 5ms once the JVM had optimised for that code path and the code was running at similar speeds to the native code that Go had produced.
Load testing
The next step was to hammer them with HTTP request using wrk.
Wrk was configured to use 2 connections and 1 thread for 60 seconds. Since the server and load tester were both running on the same machine I wanted to limit the amount of resources wrk would use.
The Go echo server results are as follows. It was able to achieve 37k requests per seconds with an average latency of 50 micro seconds and Stdev of 44 microseconds.
1 | $ wrk -t 1 -c 2 -d60s http://localhost:1323/ |
The Kotlin vertx server results are as follows. It was able to achieve 47k requests per seconds with an average latency of 271 micro seconds and Stdev of 4.8 milliseconds.
1 | $ wrk -t 1 -c 2 -d60s http://localhost:9999/ |
Conclusion
What that seems to indicate is that Vert.x was able to achieve a higher throughput but with a much wider band on response times where as Go sacrificed throughput to keep response times very tight, which makes sense since Go is optimised for low latency.
Optimising for high throughput AND low latency is quite difficult. A good read on this trade-off can be found here
Pick your technology stack based on your application requirements and not hacker news headlines.