├── .ruby-version ├── servers ├── rack_server.ru ├── .gitignore ├── plug_server │ ├── .gitignore │ ├── lib │ │ ├── server.ex │ │ └── app.ex │ └── mix.exs ├── wsgi_server.py ├── crystal_server.cr ├── servemux_server.go ├── httpbeast_server.nim ├── node_server.js └── dart_server.dart ├── .gitignore └── README.md /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.0.0 2 | -------------------------------------------------------------------------------- /servers/rack_server.ru: -------------------------------------------------------------------------------- 1 | run Proc.new { |env| ['200', {'Content-Type' => 'text/plain'}, ['Hello World']] } 2 | -------------------------------------------------------------------------------- /servers/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | nimcache 3 | go_server 4 | crystal_server 5 | nim_server 6 | pony_server 7 | -------------------------------------------------------------------------------- /servers/plug_server/.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /cover 3 | /deps 4 | erl_crash.dump 5 | *.ez 6 | ./*.beam 7 | mix.lock 8 | -------------------------------------------------------------------------------- /servers/wsgi_server.py: -------------------------------------------------------------------------------- 1 | BODY = b'Hello World' 2 | LEN = str(len(BODY)) 3 | 4 | 5 | def app(_, resp): 6 | body = BODY 7 | resp('200 OK', [('Content-Type', 'text/plain'), ('Content-Length', LEN)]) 8 | yield body 9 | -------------------------------------------------------------------------------- /servers/crystal_server.cr: -------------------------------------------------------------------------------- 1 | require "http/server" 2 | 3 | server = HTTP::Server.new do |context| 4 | context.response.content_type = "text/plain" 5 | context.response.print "Hello World" 6 | end 7 | 8 | server.listen(9292) 9 | -------------------------------------------------------------------------------- /servers/plug_server/lib/server.ex: -------------------------------------------------------------------------------- 1 | defmodule Server do 2 | import Plug.Conn 3 | 4 | def init(opts), do: opts 5 | 6 | def call(conn, _opts) do 7 | conn 8 | |> put_resp_content_type("text/plain") 9 | |> send_resp(200, "Hello world") 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /servers/servemux_server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func main() { 10 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 11 | fmt.Fprint(w, "Hello World") 12 | }) 13 | log.Fatal(http.ListenAndServe("0.0.0.0:9292", nil)) 14 | } 15 | -------------------------------------------------------------------------------- /servers/plug_server/lib/app.ex: -------------------------------------------------------------------------------- 1 | defmodule App do 2 | use Application 3 | 4 | def start(_type, _args) do 5 | children = [ 6 | Plug.Cowboy.child_spec(scheme: :http, plug: Server, options: [port: 9292]) 7 | ] 8 | 9 | opts = [strategy: :one_for_one, name: App.Supervisor] 10 | Supervisor.start_link(children, opts) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Vagrant 2 | .vagrant 3 | 4 | # Elixir 5 | /_build 6 | /cover 7 | /deps 8 | erl_crash.dump 9 | /servers/**/Gemfile.lock 10 | *.ez 11 | *.class 12 | *.beam 13 | 14 | # Python 15 | /servers/__pycache__ 16 | /servers/*.pyc 17 | /servers/.wsgi 18 | 19 | # Crystal 20 | *.dwarf 21 | 22 | # Executable 23 | crystal_server 24 | fasthttp_server 25 | httpbeast_server 26 | *.aot* 27 | -------------------------------------------------------------------------------- /servers/httpbeast_server.nim: -------------------------------------------------------------------------------- 1 | import asyncdispatch, httpbeast, options 2 | 3 | const settings = httpbeast.initSettings(Port(9292)) 4 | 5 | proc onRequest(req: Request): Future[void] = 6 | if req.httpMethod == some(HttpGet): 7 | const data = "Hello World" 8 | const headers = "Content-Type: text/plain" 9 | req.send(Http200, data, headers) 10 | 11 | run(onRequest, settings) 12 | -------------------------------------------------------------------------------- /servers/node_server.js: -------------------------------------------------------------------------------- 1 | const cluster = require('cluster'); 2 | const http = require('http'); 3 | const numCPUs = require('os').cpus().length; 4 | 5 | if (cluster.isMaster) { 6 | for (let i = 0; i < numCPUs; i++) { 7 | cluster.fork(); 8 | } 9 | } else { 10 | http.createServer((req, res) => { 11 | res.writeHead(200); 12 | res.end('Hello World'); 13 | }).listen(9292); 14 | } 15 | -------------------------------------------------------------------------------- /servers/plug_server/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule PlugServer.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :server, 6 | version: "1.2.0", 7 | elixir: "~> 1.8", 8 | start_permanent: Mix.env == :prod, 9 | deps: deps()] 10 | end 11 | 12 | def application do 13 | [applications: [:cowboy, :plug], 14 | mod: {App, []}, 15 | env: [cowboy_port: 9292]] 16 | end 17 | 18 | defp deps do 19 | [{:cowboy, "~> 2.6.3"}, 20 | {:plug, "~> 1.8"}, 21 | {:plug_cowboy, "~> 2.0.2"}] 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /servers/dart_server.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'dart:isolate'; 4 | 5 | const String _HOST = '0.0.0.0'; 6 | const String _GREET = 'Hello World'; 7 | 8 | _startServer(arg) async { 9 | var server = await HttpServer.bind(_HOST, 9292, shared: true); 10 | await for (HttpRequest request in server) { 11 | request.response 12 | ..write(_GREET) 13 | ..close(); 14 | } 15 | } 16 | 17 | void main() { 18 | final cpus = Platform.numberOfProcessors; 19 | for (int i = 0; i < cpus; i++) 20 | Isolate.spawn(_startServer, null); 21 | _startServer(null); 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Table of Contents 2 | 3 | * [Scope](#scope) 4 | * [Hello World](#hello-world) 5 | * [Disclaimer](#disclaimer) 6 | * [Languages](#languages) 7 | * [Ruby](#ruby) 8 | * [Python](#python) 9 | * [JavaScript](#javascript) 10 | * [Dart](#dart) 11 | * [Elixir](#elixir) 12 | * [Crystal](#crystal) 13 | * [Nim](#nim) 14 | * [GO](#go) 15 | * [Tools](#tools) 16 | * [Wrk](#wrk) 17 | * [Platform](#platform) 18 | * [RAM and CPU](#ram-and-cpu) 19 | * [Benchmarks](#benchmarks) 20 | * [Results](#results) 21 | * [Puma](#puma) 22 | * [Gunicorn with Meinheld](#gunicorn-with-meinheld) 23 | * [Node Cluster](#node-cluster) 24 | * [Dart HttpServer](#dart-httpserver) 25 | * [Plug with Cowboy](#plug-with-cowboy) 26 | * [Crystal HTTP](#crystal-http) 27 | * [httpbeast](#httpbeast) 28 | * [GO ServeMux](#go-servermux) 29 | * [Hyper](#hyper) 30 | 31 | ## Scope 32 | The idea behind this repository is to benchmark different languages implementation of HTTP server. 33 | 34 | ### Hello World 35 | The *application* i tested is minimal: the HTTP version of the *Hello World* example. 36 | This approach allows including languages i barely know, since it is pretty easy to find such implementation online. 37 | If you're looking for more complex examples, you will have better luck with the [TechEmpower benchmarks](https://www.techempower.com/benchmarks/). 38 | 39 | ### Disclaimer 40 | Please do take the following numbers with a grain of salt: it is not my intention to promote one language over another basing on micro-benchmarks. 41 | Indeed you should never pick a language just basing on its presumed performance. 42 | 43 | ## Languages 44 | I have became lazy with years and just adopt languages i can install via `homebrew`, sorry Oracle/MS. This also allows me to benchmark them in a single session, thus trying to use an environment as neutral as possible. 45 | Where possible i just relied on the standard library, but when it is not production-ready (i.e. Ruby, Python). 46 | 47 | ### Ruby 48 | [Ruby](https://www.ruby-lang.org/en/) 3.0.0 is used. 49 | Ruby is a general-purpose, interpreted, dynamic programming language, focused on simplicity and productivity. 50 | 51 | ### Python 52 | [Python](https://www.python.org/) 3.9.1 is used. 53 | Python is a widely used high-level, general-purpose, interpreted, dynamic programming language. 54 | 55 | ### JavaScript 56 | [Node.js](https://nodejs.org/en/) version 15.5.0 is used. 57 | Node.js is based on the V8 JavaScript engine, optimized by Google and supporting most of the new language's features. 58 | 59 | ### Dart 60 | [Dart](https://www.dartlang.org/) version 2.10.4 is used. 61 | Dart is a VM based, object-oriented, sound typed language using a C-style syntax that transcompiles optionally into JavaScript. 62 | 63 | ### Elixir 64 | [Elixir](http://elixir-lang.org/) 1.11.2 is used. 65 | Elixir is a purely functional language that runs on the [Erlang](https://www.erlang.org/) VM and is strongly influenced by the Ruby syntax. 66 | 67 | ### Crystal 68 | [Crystal](http://crystal-lang.org/) 0.35.1 is used. 69 | Crystal has a syntax very close to Ruby, but brings some desirable features such as statically typing and ahead of time (AOT) compilation. 70 | 71 | ### Nim 72 | [Nim](http://nim-lang.org/) 1.4.2 is used. 73 | Nim is an AOT, Python inspired, statically typed language that comes with an ambitious compiler aimed to produce code in C, C++, JavaScript or ObjectiveC. 74 | 75 | ### GO 76 | [GO](https://golang.org/) 1.15.6 is used. 77 | GO is an AOT language that focuses on simplicity and offers a broad standard library with [CSP](https://en.wikipedia.org/wiki/Communicating_sequential_processes) constructs built in. 78 | 79 | ## Tools 80 | 81 | ### Wrk 82 | I used [wrk](https://github.com/wg/wrk) as the loading tool. 83 | I measured each application server six times, picking the best lap (but for VM based languages demanding longer warm-up). 84 | ```shell 85 | wrk -t 4 -c 100 -d30s --timeout 2000 http://0.0.0.0:9292 86 | ``` 87 | 88 | ### Platform 89 | These benchmarks are recorded on a MacBook PRO 13 2019 having these specs: 90 | * macOS Catalina 91 | * 1.4 GHz Quad-Core Intel Core i5 92 | * 8 GB 2133 MHz LPDDR3 93 | 94 | ### RAM and CPU 95 | I measured RAM and CPU consumption by using macOS Activity Monitor dashboard and recording max consumption peak. 96 | For the languages relying on pre-forking parallelism i reported the average consumption by taking a snapshot during the stress period. 97 | 98 | ## Benchmarks 99 | 100 | ### Results 101 | | Language | App Server | Requests/sec | RAM (MB) | CPU (%) | 102 | | :------------------------ | :------------------------------------------------ | ----------------: |---------: |--------: | 103 | | [Ruby+MJIT](#ruby) | [Puma](#puma) | 36455.88 | > 100 | > 580 | 104 | | [Elixir](#elixir) | [Plug with Cowboy](#plug-with-cowboy) | 46416.25 | 50.5 | 583.8 | 105 | | [Ruby](#ruby) | [Puma](#puma) | 47975.36 | > 100 | > 580 | 106 | | [Dart](#dart) | [Dart HttpServer](#dart-httpserver) | 59335.33 | 193.2 | 429.1 | 107 | | [JavaScript](#javascript) | [Node Cluster](#node-cluster) | 87208.47 | > 200 | > 240 | 108 | | [GO](#go) | [GO ServeMux](#go-servemux) | 103847.10 | 10.0 | 429.1 | 109 | | [Python](#python) | [Gunicorn with Meinheld](#gunicorn-with-meinheld) | 120105.65 | > 40 | > 380 | 110 | | [Nim](#nim) | [httpbeast](#httpbeast) | 128257.98 | 11.4 | 99.6 | 111 | | [Crystal](#crystal) | [Crystal HTTP](#crystal-http) | 132699.78 | 8.5 | 246.7 | 112 | 113 | 114 | ### Puma 115 | I tested Ruby by using a plain [Rack](http://rack.github.io/) application served by [Puma](http://puma.io). 116 | 117 | #### Bootstrap 118 | ```shell 119 | RUBYOPT='--jit' puma -w 8 -t 2 --preload servers/rack_server.ru 120 | ``` 121 | 122 | 123 | ### Gunicorn with Meinheld 124 | I tested Python by using [Gunicorn](http://gunicorn.org/) spawning [Meinheld](http://meinheld.org/) workers with a plain WSGI compliant server. 125 | 126 | #### Bootstrap 127 | ```shell 128 | cd servers 129 | gunicorn -w 4 -k meinheld.gmeinheld.MeinheldWorker -b :9292 wsgi_server:app 130 | ``` 131 | 132 | 133 | ### Node Cluster 134 | I used the cluster module included into Node's standard library. 135 | 136 | #### Bootstrap 137 | ```shell 138 | node servers/node_server.js 139 | ``` 140 | 141 | 142 | ### Dart HttpServer 143 | I used the async HTTP server embedded into the Dart standard library and compiled it with `dart2native` AOT compiler. 144 | 145 | #### Bootstrap 146 | ```shell 147 | dart2native servers/dart_server.dart -k aot 148 | dartaotruntime servers/dart_server.aot 149 | ``` 150 | 151 | 152 | ### Plug with Cowboy 153 | I tested Elixir by using [Plug](https://github.com/elixir-lang/plug) library that provides a [Cowboy](https://github.com/ninenines/cowboy) adapter. 154 | 155 | #### Bootstrap 156 | ```shell 157 | cd servers/plug_server 158 | MIX_ENV=prod mix compile 159 | MIX_ENV=prod mix run --no-halt 160 | ``` 161 | 162 | 163 | ### Crystal HTTP 164 | I used Crystal HTTP server standard library, enabling parallelism by using the `preview_mt` flag. 165 | 166 | #### Bootstrap 167 | ```shell 168 | crystal build -Dpreview_mt --release servers/crystal_server.cr 169 | ./crystal_server 170 | ``` 171 | 172 | 173 | ### httpbeast 174 | To test Nim i opted for the [httpbeast](https://github.com/dom96/httpbeast) library: an asynchronous server relying on Nim HTTP standard library. 175 | 176 | #### Bootstrap 177 | ```shell 178 | nim c -d:release --threads:on servers/httpbeast_server.nim 179 | ./servers/httpbeast_server 180 | ``` 181 | 182 | 183 | ### GO ServeMux 184 | I used the [HTTP ServeMux](https://golang.org/pkg/net/http/) GO standard library. 185 | 186 | #### Bootstrap 187 | ```shell 188 | go run servers/servemux_server.go 189 | ``` 190 | --------------------------------------------------------------------------------