diff --git a/README.md b/README.md index c734398..1a59022 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ⚡zap⚡ - blazingly fast backends +# ⚡zig zap⚡ - blazingly fast backends Zap is intended to become the [zig](https://ziglang.org) replacement for the kind of REST APIs I used to write in [python](https://python.org) with @@ -37,6 +37,25 @@ I'll continue wrapping more of facil.io's functionality and adding stuff to zap to a point where I can use it as the JSON REST API backend for real research projects, serving thousands of concurrent clients. +## ⚡blazingly fast⚡ + +Claiming to be blazingly fast is the new black. At least, zap doesn't slow you +down and if your server performs poorly, it's probably not exactly zap's fault. +Zap relies on the [facil.io](https://facil.io) framework and so it can't really +claim any performance things for itself. In this initial implementation of zap, +I didn't care about optimizations at all. + +But, how fast is it? Being blazingly fast is relative. When compared with a +simple GO HTTP server, a simple zig zap HTTP server performed about twice as +fast - on my machine. + +![](wrk_summary.png) + +So, being somewhere in the ballpark of basic GO performance, zig zap seems to be +... of reasonable performance 😎. + +See more details in [blazingly-fast.md](blazingly-fast.md). + ## Getting started ```shell diff --git a/blazingly-fast.md b/blazingly-fast.md new file mode 100644 index 0000000..ea08bc8 --- /dev/null +++ b/blazingly-fast.md @@ -0,0 +1,326 @@ +# ⚡blazingly fast⚡ + +I conducted a series of quick tests, using wrk with simple HTTP servers written +in GO and in zig zap. + +Just to get some sort of indication, I also included measurements for python +since I used to write my REST APIs in python before creating zig zap. + +You can check out the scripts I used for the tests in [./wrk](wrk/). + +## results + +You can see the verbatim output of `wrk`, and some infos about the test machine +below the code snippets. + +### requests / sec +![](wrk_requests.png) + +### transfer MB / sec + +![](wrk_transfer.png) + + +## zig code + +zig version .11.0-dev.1265+3ab43988c + +```zig +const std = @import("std"); +const zap = @import("zap"); + +fn on_request_minimal(r: zap.SimpleRequest) void { + _ = r.sendBody("

Hello from ZAP!!!

"); +} + +pub fn main() !void { + var listener = zap.SimpleHttpListener.init(.{ + .port = 3000, + .on_request = on_request_minimal, + .log = false, + .max_clients = 100000, + }); + try listener.listen(); + + std.debug.print("Listening on 0.0.0.0:3000\n", .{}); + + // start worker threads + zap.start(.{ + .threads = 4, + .workers = 4, + }); +} +``` + +## go code + +go version go1.16.9 linux/amd64 + +```go +package main + +import ( + "fmt" + "net/http" +) + +func hello(w http.ResponseWriter, req *http.Request) { + fmt.Fprintf(w, "hello from GO!!!\n") +} + +func main() { + print("listening on 0.0.0.0:8090\n") + http.HandleFunc("/hello", hello) + http.ListenAndServe(":8090", nil) +} +``` + +## python code + +python version 3.9.6 + +```python +# Python 3 server example +from http.server import BaseHTTPRequestHandler, HTTPServer + +hostName = "127.0.0.1" +serverPort = 8080 + + +class MyServer(BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.send_header("Content-type", "text/html") + self.end_headers() + self.wfile.write(bytes("HELLO FROM PYTHON!!!", "utf-8")) + + def log_message(self, format, *args): + return + + +if __name__ == "__main__": + webServer = HTTPServer((hostName, serverPort), MyServer) + print("Server started http://%s:%s" % (hostName, serverPort)) + + try: + webServer.serve_forever() + except KeyboardInterrupt: + pass + + webServer.server_close() + print("Server stopped.") +``` + +## wrk output + +wrk version: `wrk 4.1.0 [epoll] Copyright (C) 2012 Will Glozer` + +``` +======================================================================== + zig +======================================================================== +Running 10s test @ http://127.0.0.1:3000 + 4 threads and 400 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 337.49us 109.24us 4.21ms 88.64% + Req/Sec 157.78k 8.31k 172.93k 59.25% + Latency Distribution + 50% 314.00us + 75% 345.00us + 90% 396.00us + 99% 699.00us + 6280964 requests in 10.01s, 1.13GB read +Requests/sec: 627277.99 +Transfer/sec: 116.05MB + + +(base) rs@ryzen:~/code/github.com/renerocksai/zap$ ./wrk/measure.sh zig +Listening on 0.0.0.0:3000 +======================================================================== + zig +======================================================================== +Running 10s test @ http://127.0.0.1:3000 + 4 threads and 400 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 339.50us 176.93us 12.52ms 91.23% + Req/Sec 158.24k 8.00k 170.45k 53.00% + Latency Distribution + 50% 313.00us + 75% 345.00us + 90% 399.00us + 99% 697.00us + 6297146 requests in 10.02s, 1.14GB read +Requests/sec: 628768.81 +Transfer/sec: 116.33MB + + +(base) rs@ryzen:~/code/github.com/renerocksai/zap$ ./wrk/measure.sh zig +Listening on 0.0.0.0:3000 +======================================================================== + zig +======================================================================== +Running 10s test @ http://127.0.0.1:3000 + 4 threads and 400 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 344.23us 185.46us 14.44ms 89.93% + Req/Sec 157.56k 12.85k 171.21k 94.00% + Latency Distribution + 50% 312.00us + 75% 346.00us + 90% 528.00us + 99% 747.00us + 6270913 requests in 10.02s, 1.13GB read +Requests/sec: 625756.37 +Transfer/sec: 115.77MB + + + +(base) rs@ryzen:~/code/github.com/renerocksai/zap$ ./wrk/measure.sh go +listening on 0.0.0.0:8090 +======================================================================== + go +======================================================================== +Running 10s test @ http://127.0.0.1:8090/hello + 4 threads and 400 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 721.60us 755.26us 14.12ms 87.95% + Req/Sec 123.79k 4.65k 135.57k 69.00% + Latency Distribution + 50% 429.00us + 75% 0.88ms + 90% 1.65ms + 99% 3.63ms + 4927352 requests in 10.02s, 629.68MB read +Requests/sec: 491607.70 +Transfer/sec: 62.82MB + + +(base) rs@ryzen:~/code/github.com/renerocksai/zap$ ./wrk/measure.sh go +listening on 0.0.0.0:8090 +======================================================================== + go +======================================================================== +Running 10s test @ http://127.0.0.1:8090/hello + 4 threads and 400 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 698.35us 707.23us 11.03ms 87.80% + Req/Sec 124.36k 4.27k 135.19k 69.50% + Latency Distribution + 50% 419.00us + 75% 0.86ms + 90% 1.58ms + 99% 3.38ms + 4948380 requests in 10.01s, 632.37MB read +Requests/sec: 494338.77 +Transfer/sec: 63.17MB + + +(base) rs@ryzen:~/code/github.com/renerocksai/zap$ ./wrk/measure.sh go +listening on 0.0.0.0:8090 +======================================================================== + go +======================================================================== +Running 10s test @ http://127.0.0.1:8090/hello + 4 threads and 400 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 700.97us 710.99us 10.38ms 87.73% + Req/Sec 124.38k 4.16k 135.31k 67.25% + Latency Distribution + 50% 419.00us + 75% 0.86ms + 90% 1.59ms + 99% 3.39ms + 4950585 requests in 10.01s, 632.65MB read +Requests/sec: 494551.24 +Transfer/sec: 63.20MB + + + +(base) rs@ryzen:~/code/github.com/renerocksai/zap$ ./wrk/measure.sh python +Server started http://127.0.0.1:8080 +======================================================================== + python +======================================================================== +Running 10s test @ http://127.0.0.1:8080 + 4 threads and 400 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 4.55ms 57.21ms 1.66s 99.12% + Req/Sec 2.56k 2.55k 7.57k 57.43% + Latency Distribution + 50% 216.00us + 75% 343.00us + 90% 371.00us + 99% 765.00us + 27854 requests in 10.02s, 3.61MB read + Socket errors: connect 0, read 27854, write 0, timeout 8 +Requests/sec: 2779.87 +Transfer/sec: 369.20KB + + +(base) rs@ryzen:~/code/github.com/renerocksai/zap$ ./wrk/measure.sh python +Server started http://127.0.0.1:8080 +======================================================================== + python +======================================================================== +Running 10s test @ http://127.0.0.1:8080 + 4 threads and 400 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 4.08ms 58.22ms 1.66s 99.27% + Req/Sec 2.28k 2.13k 7.68k 50.00% + Latency Distribution + 50% 226.00us + 75% 345.00us + 90% 374.00us + 99% 496.00us + 55353 requests in 10.03s, 7.18MB read + Socket errors: connect 0, read 55353, write 0, timeout 8 +Requests/sec: 5521.48 +Transfer/sec: 733.33KB + + +(base) rs@ryzen:~/code/github.com/renerocksai/zap$ ./wrk/measure.sh python +Server started http://127.0.0.1:8080 +======================================================================== + python +======================================================================== +Running 10s test @ http://127.0.0.1:8080 + 4 threads and 400 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 6.05ms 70.02ms 1.66s 98.94% + Req/Sec 2.40k 2.35k 7.43k 54.40% + Latency Distribution + 50% 222.00us + 75% 313.00us + 90% 366.00us + 99% 162.93ms + 31959 requests in 10.02s, 4.15MB read + Socket errors: connect 0, read 31959, write 0, timeout 6 +Requests/sec: 3189.81 +Transfer/sec: 423.65KB +``` + +## test machine + +``` + ▗▄▄▄ ▗▄▄▄▄ ▄▄▄▖ rs@ryzen + ▜███▙ ▜███▙ ▟███▛ -------- + ▜███▙ ▜███▙▟███▛ OS: NixOS 22.05 (Quokka) x86_64 + ▜███▙ ▜██████▛ Host: Micro-Star International Co., Ltd. B550-A PRO (MS-7C56) + ▟█████████████████▙ ▜████▛ ▟▙ Kernel: 6.0.15 + ▟███████████████████▙ ▜███▙ ▟██▙ Uptime: 7 days, 5 hours, 29 mins + ▄▄▄▄▖ ▜███▙ ▟███▛ Packages: 5950 (nix-system), 893 (nix-user), 5 (flatpak) + ▟███▛ ▜██▛ ▟███▛ Shell: bash 5.1.16 + ▟███▛ ▜▛ ▟███▛ Resolution: 3840x2160 +▟███████████▛ ▟██████████▙ DE: none+i3 +▜██████████▛ ▟███████████▛ WM: i3 + ▟███▛ ▟▙ ▟███▛ Terminal: Neovim Terminal + ▟███▛ ▟██▙ ▟███▛ CPU: AMD Ryzen 5 5600X (12) @ 3.700GHz + ▟███▛ ▜███▙ ▝▀▀▀▀ GPU: AMD ATI Radeon RX 6700/6700 XT / 6800M + ▜██▛ ▜███▙ ▜██████████████████▛ Memory: 10378MiB / 32033MiB + ▜▛ ▟████▙ ▜████████████████▛ + ▟██████▙ ▜███▙ + ▟███▛▜███▙ ▜███▙ + ▟███▛ ▜███▙ ▜███▙ + ▝▀▀▀ ▀▀▀▀▘ ▀▀▀▘ +``` + diff --git a/build.zig b/build.zig index 119aa00..ea819a0 100644 --- a/build.zig +++ b/build.zig @@ -24,6 +24,7 @@ pub fn build(b: *std.build.Builder) !void { .{ .name = "serve", .src = "examples/serve/serve.zig" }, .{ .name = "hello_json", .src = "examples/hello_json/hello_json.zig" }, .{ .name = "endpoints", .src = "examples/endpoints/main.zig" }, + .{ .name = "wrk", .src = "wrk/zig/main.zig" }, }) |excfg| { const ex_name = excfg.name; const ex_src = excfg.src; @@ -63,14 +64,7 @@ pub fn build(b: *std.build.Builder) !void { } } -fn logstep(msg: []const u8) void { - std.debug.print("=================================================\n", .{}); - std.debug.print("== STEP : {s}\n", .{msg}); - std.debug.print("=================================================\n", .{}); -} - pub fn ensureDeps(step: *std.build.Step) !void { - logstep("ENSURE DEPS"); _ = step; const allocator = std.heap.page_allocator; ensureGit(allocator); diff --git a/shell.nix b/shell.nix index efdc414..781ce5c 100644 --- a/shell.nix +++ b/shell.nix @@ -9,5 +9,22 @@ } } : pkgs.mkShell { - nativeBuildInputs = [ pkgs.neovim-nightly pkgs.bat pkgs.wrk]; + nativeBuildInputs = [ + pkgs.neovim-nightly + pkgs.bat + pkgs.wrk + pkgs.python3 + ]; + + buildInputs = [ + pkgs.go + pkgs.gotools + pkgs.gopls + # pkgs.go-outline + # pkgs.gocode + # pkgs.gopkgs + # pkgs.gocode-gomod + # pkgs.godef + pkgs.golint + ]; } diff --git a/wrk/go/main b/wrk/go/main new file mode 100755 index 0000000..62ddece Binary files /dev/null and b/wrk/go/main differ diff --git a/wrk/go/main.go b/wrk/go/main.go new file mode 100644 index 0000000..508f696 --- /dev/null +++ b/wrk/go/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "fmt" + "net/http" +) + +func hello(w http.ResponseWriter, req *http.Request) { + fmt.Fprintf(w, "hello from GO!!!\n") +} + +func main() { + print("listening on 0.0.0.0:8090\n") + http.HandleFunc("/hello", hello) + http.ListenAndServe(":8090", nil) +} diff --git a/wrk/measure.sh b/wrk/measure.sh new file mode 100755 index 0000000..a183b45 --- /dev/null +++ b/wrk/measure.sh @@ -0,0 +1,41 @@ +#! /usr/bin/env bash +THREADS=4 +CONNECTIONS=400 +DURATION_SECONDS=10 + +SUBJECT=$1 + + +if [ "$SUBJECT" = "" ] ; then + echo "usage: $0 subject # subject: zig or go" + exit 1 +fi + +if [ "$SUBJECT" = "zig" ] ; then + zig build -Drelease-fast wrk > /dev/null + ./zig-out/bin/wrk & + PID=$! + URL=http://127.0.0.1:3000 +fi + +if [ "$SUBJECT" = "go" ] ; then + cd wrk/go && go build main.go + ./main & + PID=$! + URL=http://127.0.0.1:8090/hello +fi + +if [ "$SUBJECT" = "python" ] ; then + python wrk/python/main.py & + PID=$! + URL=http://127.0.0.1:8080 +fi + +sleep 1 +echo "========================================================================" +echo " $SUBJECT" +echo "========================================================================" +wrk -c $CONNECTIONS -t $THREADS -d $DURATION_SECONDS --latency $URL + +kill $PID + diff --git a/wrk/python/main.py b/wrk/python/main.py new file mode 100644 index 0000000..13226e3 --- /dev/null +++ b/wrk/python/main.py @@ -0,0 +1,29 @@ +# Python 3 server example +from http.server import BaseHTTPRequestHandler, HTTPServer + +hostName = "127.0.0.1" +serverPort = 8080 + + +class MyServer(BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.send_header("Content-type", "text/html") + self.end_headers() + self.wfile.write(bytes("HELLO FROM PYTHON!!!", "utf-8")) + + def log_message(self, format, *args): + return + + +if __name__ == "__main__": + webServer = HTTPServer((hostName, serverPort), MyServer) + print("Server started http://%s:%s" % (hostName, serverPort)) + + try: + webServer.serve_forever() + except KeyboardInterrupt: + pass + + webServer.server_close() + print("Server stopped.") diff --git a/wrk/zig/main.zig b/wrk/zig/main.zig new file mode 100644 index 0000000..45cb70e --- /dev/null +++ b/wrk/zig/main.zig @@ -0,0 +1,24 @@ +const std = @import("std"); +const zap = @import("zap"); + +fn on_request_minimal(r: zap.SimpleRequest) void { + _ = r.sendBody("

Hello from ZAP!!!

"); +} + +pub fn main() !void { + var listener = zap.SimpleHttpListener.init(.{ + .port = 3000, + .on_request = on_request_minimal, + .log = false, + .max_clients = 100000, + }); + try listener.listen(); + + std.debug.print("Listening on 0.0.0.0:3000\n", .{}); + + // start worker threads + zap.start(.{ + .threads = 4, + .workers = 4, + }); +} diff --git a/wrk_requests.png b/wrk_requests.png new file mode 100644 index 0000000..5b98a96 Binary files /dev/null and b/wrk_requests.png differ diff --git a/wrk_summary.png b/wrk_summary.png new file mode 100644 index 0000000..d7d6ada Binary files /dev/null and b/wrk_summary.png differ diff --git a/wrk_transfer.png b/wrk_transfer.png new file mode 100644 index 0000000..3358a39 Binary files /dev/null and b/wrk_transfer.png differ