Skip to content

Commit

Permalink
continue writing README
Browse files Browse the repository at this point in the history
  • Loading branch information
mookums committed Sep 15, 2024
1 parent a1b62d1 commit 6a7630b
Show file tree
Hide file tree
Showing 9 changed files with 228 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
zig-out/
.zig-cache/
perf*
perf*.data*
heaptrack*
35 changes: 31 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
# zzz
![zzz logo](./docs/img/zzz.png)


## Installing
Tracking Latest Zig Stable: `0.13.0`
```
zig fetch --save git+https://github.com/mookums/zzz#main
```

You can then add the dependency in your `build.zig` file:
```
const zzz = b.dependency("zzz", .{
.target = target,
.optimize = optimize,
}).module("zzz");
`zig fetch --save https://github.com/mookums/zzz/archive/main.tar.gz`
exe.root_module.addImport(zzz);
```

## zzz?
zzz is a framework for writing performant and reliable networked services in Zig. It currently only supports TCP as the underlying transport layer but allows for any arbitrary protocol to run on top.
Expand All @@ -12,15 +25,29 @@ zzz is a framework for writing performant and reliable networked services in Zig

It focuses on modularity and portability, allowing you to swap in your own implementations for various things. Consumers can provide both a protocol and an async implementation, allowing for maximum flexibility. This allows for use in standard servers as well as embedded/bare metal domains.

For more information, look here:
1. [Getting Started](./docs/getting_started.md)
2. [HTTPS](./docs/https.md)
3. [Performance Tuning](./docs/performance.md)
4. [Custom Async](https://muki.gg/post/modular-async)

## Optimization
zzz is **very** fast. Through a combination of methods, such as allocation at start up and avoiding thread contention, we are able to extract tons of performance.
zzz is **very** fast. Through a combination of methods, such as allocation at start up and avoiding thread contention, we are able to extract tons of performance out of a fairly simple implementation. zzz is quite robust currently but is still early stage software. It's currently been running in production, serving my [site](https://muki.gg).

We are nearly as fast as gnet (zzz is 2% slower at 1000 concurrent connections), the fastest plaintext HTTP server according to [TechEmpower](https://www.techempower.com/benchmarks/#hw=ph&test=plaintext&section=data-r22), while consuming only ~21% of the memory that gnet requires.

![benchmark (request per sec)](./docs/benchmark/req_per_sec_ccx63_24.png)
[Raw Data](./docs/benchmark/request_ccx63_24.csv)

![benchmark (peak memory)](./docs/benchmark/peak_memory_ccx63_24.png)
[Raw Data](./docs/benchmark/memory_ccx63_24.csv)

zzz currently out performs both [http.zig](https://github.com/karlseguin/http.zig) and [zap](https://github.com/zigzap/zap), while being almost entirely written in Zig.
On the CCX63 instance on Hetzner, we are 66.4% faster than [zap](https://github.com/zigzap/zap) and 77% faster than [http.zig](https://github.com/karlseguin/http.zig). We also utilize less memory, using only ~3% of the memory used by zap and ~18% of the memory used by http.zig.

zzz can be configured to utilize minimal memory while remaining performant. The provided `minram` example only uses 392 kB!

## Features
- [Modular Asyncronous Implementation](https://muki.gg/post/modular-async)
- [Modular Asynchronous Implementation](https://muki.gg/post/modular-async)
- Allows for passing in your own Async implementation.
- Comes with:
- io_uring for Linux.
Expand Down
9 changes: 9 additions & 0 deletions docs/benchmark/memory_ccx63_24.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
memory,server
38056,axum
61780,bun
41668,fasthttp
32948,gnet
59700,go
38236,httpz
195008,zap
7052,zzz
Binary file added docs/benchmark/peak_memory_ccx63_24.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/benchmark/req_per_sec_ccx63_24.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions docs/benchmark/request_ccx63_24.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
connections,axum,bun,fasthttp,gnet,go,httpz,zap,zzz
50,292494.58,86609.16,420978.42,641544.53,242809.47,563888.86,333257.06,682125.41
100,318314.85,93125.52,433737.86,1440465.37,295712.45,835373.59,461449.89,1351331.55
150,418083.22,96731.65,478544.23,1674851.55,371783.69,944743.87,582542.04,1596746.32
200,542204.09,98701.27,465290.37,1699966.17,410363.67,990713.34,706177.30,1676979.94
250,666151.93,98789.38,496261.37,1722192.44,442345.35,1013771.19,839284.69,1700670.45
300,751113.09,96734.70,501222.89,1731791.52,470631.08,1033773.33,894527.96,1717554.29
350,824279.40,98305.91,486883.05,1735827.72,490819.69,1041804.22,922445.57,1718380.77
400,888603.71,98472.03,482811.08,1738149.71,520029.71,1041222.99,936436.48,1724530.86
450,950975.01,98472.87,478958.55,1746923.67,535367.31,1035830.98,962752.92,1720942.48
500,1003966.00,97784.31,519397.49,1751650.56,549010.42,1043498.23,968951.67,1719846.89
550,1041859.40,98287.86,514302.26,1748460.78,557684.60,1035441.71,1020923.15,1722315.16
600,1126662.51,95892.56,508380.89,1754027.28,579099.73,1033071.22,1016071.86,1730779.35
650,1155202.40,96152.20,503744.42,1754384.48,592089.27,1028834.30,1010675.46,1719370.81
700,1191630.07,90903.25,504706.87,1750663.29,594405.02,1032906.41,1011051.00,1722991.03
750,1207422.21,92513.45,498905.86,1752630.96,604523.17,1019253.63,1010635.35,1712627.00
800,1233106.73,94833.81,498889.37,1758646.45,609649.33,1005188.32,1015001.88,1720472.98
850,1254669.17,94899.79,494205.47,1749606.84,613396.18,992851.46,1016934.30,1717614.81
900,1255524.58,92979.35,527261.08,1747810.93,614263.06,982815.79,1014644.16,1713303.31
950,1272252.96,94233.75,524356.14,1748290.91,624776.65,969787.77,1019330.69,1710140.01
1000,1282654.15,92645.91,524116.12,1748164.23,630915.05,967014.73,1028809.30,1711976.85
57 changes: 57 additions & 0 deletions docs/getting_started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Getting Started
zzz is a networking framework that allows for modularity and flexibility in design. For most use cases, this flexibility is not a requirement and so various defaults are provided.

For this guide, we will assume that you are running on a modern Linux platform and looking to design a service that utilizes HTTP.

`zig fetch --save https://github.com/mookums/zzz/archive/main.tar.gz`

## Hello, World!
We can write a quick example that serves out "Hello, World" responses to any client that connects to the server. This example is the same as the one that is provided within the `src/examples/basic` directory.

```zig
const std = @import("std");
const zzz = @import("zzz");
const http = zzz.HTTP;
const log = std.log.scoped(.@"examples/basic");
pub fn main() !void {
const host: []const u8 = "0.0.0.0";
const port: u16 = 9862;
const allocator = std.heap.page_allocator;
var router = http.Router.init(allocator);
defer router.deinit();
try router.serve_route("/", http.Route.init().get(struct {
pub fn handler_fn(_: http.Request, response: *http.Response, _: http.Context) void {
const body =
\\ <!DOCTYPE html>
\\ <html>
\\ <body>
\\ <h1>Hello, World!</h1>
\\ </body>
\\ </html>
;
response.set(.{
.status = .OK,
.mime = http.Mime.HTML,
.body = body[0..],
});
}
}.handler_fn));
var server = http.Server(.plain).init(.{
.allocator = allocator,
}, null);
defer server.deinit();
try server.bind(host, port);
try server.listen(.{
.router = &router,
});
}
```

The snippet above handles all of the basic tasks involved with serving a plaintext route using zzz's HTTP implementation.
63 changes: 63 additions & 0 deletions docs/https.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# HTTPS
zzz utilizes [BearSSL](https://bearssl.org/) to provide a safe and performant TLS implementation. This TLS functionality is entirely separated from the I/O for maximum portability.

*Note: TLS Support is not **entirely** complete yet. It's a very rough area that will be getting cleaned up in a future development cycle*

## TLS Example
This is derived from the example at `src/examples/tls` and utilizes some certificates that are present within the repository.
```zig
const std = @import("std");
const zzz = @import("zzz");
const http = zzz.HTTP;
const log = std.log.scoped(.@"examples/tls");
pub fn main() !void {
const host: []const u8 = "0.0.0.0";
const port: u16 = 9862;
const allocator = std.heap.page_allocator;
var router = http.Router.init(allocator);
defer router.deinit();
try router.serve_route("/", http.Route.init().get(struct {
pub fn handler_fn(_: http.Request, response: *http.Response, _: http.Context) void {
const body =
\\ <!DOCTYPE html>
\\ <html>
\\ <body>
\\ <h1>Hello, World!</h1>
\\ </body>
\\ </html>
;
response.set(.{
.status = .OK,
.mime = http.Mime.HTML,
.body = body[0..],
});
}
}.handler_fn));
var server = http.Server(.{
.tls = .{
.cert = .{
.file = .{ .path = "src/examples/tls/certs/cert.pem" },
},
.key = .{
.file = .{ .path = "src/examples/tls/certs/key.pem" },
},
.cert_name = "CERTIFICATE",
.key_name = "EC PRIVATE KEY",
},
}).init(.{
.allocator = allocator,
}, null);
defer server.deinit();
try server.bind(host, port);
try server.listen(.{ .router = &router });
}
```
This example above passes the `.tls` variant of the enum to the HTTP Server and provides the location of the certificate and key to be used. It also has the functionality to pass in a buffer containing the cert and key data if that is preferable. You must also provide the certificate and key name as the PEM format allows for multiple items to be placed within the same file.

46 changes: 46 additions & 0 deletions docs/performance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
## Performance
zzz's design philosophy results in a lot of knobs that the consumer of the library can turn and tune to their preference.

These performance tips are general and can apply to any protocol implementation. HTTP is used as the general example because it is currently the only completed protocol.

## Performance Hunting
When seeking out maximum performance, one of the most important settings to change is the `.threading` setting of the server. By default, zzz runs in a single threaded mode (likely to change in a future development cycle).

```zig
var server = http.Server(.plain).init(.{
.allocator = allocator,
}, null);
```
This means that you can gain the largest performance boon by simply adding this one line:
```zig
var server = http.Server(.plain).init(.{
.allocator = allocator,
.threading = .{ .multi_threaded = .auto },
}, null);
```

The most important part of switching to the multi threaded model is using a **thread-safe** allocator. I tend to use the page allocator but I believe the general purpose allocator can also be thread-safe.

Other settings of note include:
- `size_connection_arena_retain` which controls how much memory that has been allocated within a connection's arena will be retained for the next connection.
- `size_connections_max` which controls the maximum number of connections that each thread can handle. Any connection after this number will get closed.

## Minimizing Memory Usage
When using zzz in certain environments, your goal may be to reduce memory usage. zzz provides a variety of controls for handling how much memory is allocated at start up.

```zig
var server = http.Server(.plain).init(.{
.allocator = allocator,
.size_backlog = 32,
.size_connections_max = 16,
.size_connection_arena_retain = 64,
.size_socket_buffer = 512,
}, null);
```

There is no overarching setting here but a selection of ones you can tune to minimize:
- run in single threaded mode
- `size_connections_max` can be reduced. This value is used internally as every connection on every thread owns a `Provision`.
- `size_connection_arena_retain` can be reduced. If you are not doing any allocations within the handler, you can even set this to 0.
- `size_socket_buffer` can be reduced. There is a lower limit to this value of around 128 bytes. (This will be changed in the future and internal buffers will be separated).

0 comments on commit 6a7630b

Please sign in to comment.