Skip to content

Commit 15f33bf

Browse files
committed
new post leveraging zig allocator
1 parent 517c4eb commit 15f33bf

File tree

1 file changed

+159
-0
lines changed

1 file changed

+159
-0
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
---
2+
title: "Zig 分配器的应用"
3+
date: 2024-06-16T12:11:44+0800
4+
---
5+
6+
> 原文地址: <https://www.openmymind.net/Leveraging-Zigs-Allocators/>
7+
8+
假设我们想为Zig编写一个 [HTTP服务器库](https://github.com/karlseguin/http.zig)。这个库的核心可能是线程池,用于处理请求。以简化的方式来看,它可能类似于:
9+
10+
```zig
11+
fn run(worker: *Worker) void {
12+
while (queue.pop()) |conn| {
13+
const action = worker.route(conn.req.url);
14+
action(conn.req, conn.res) catch { // TODO: 500 };
15+
worker.write(conn.res);
16+
}
17+
}
18+
```
19+
20+
作为这个库的用户,您可能会编写一些动态内容的操作。如果假设在启动时为服务器提供分配器(Allocator),则可以将此分配器传递给动作:
21+
22+
```zig
23+
fn run(worker: *Worker) void {
24+
const allocator = worker.server.allocator;
25+
while (queue.pop()) |conn| {
26+
const action = worker.route(conn.req.url);
27+
action(allocator, conn.req, conn.res) catch { // TODO: 500 };
28+
worker.write(conn.res);
29+
}
30+
}
31+
```
32+
33+
这允许用户编写如下的操作:
34+
35+
```zig
36+
fn greet(allocator: Allocator, req: *http.Request, res: *http.Response) !void {
37+
const name = req.query("name") orelse "guest";
38+
res.status = 200;
39+
res.body = try std.fmt.allocPrint(allocator, "Hello {s}", .{name});
40+
}
41+
```
42+
43+
虽然这是一个正确的方向,但存在明显的问题:分配的问候语从未被释放。我们的`run`函数不能在写回应后就调用`allocator.free(conn.res.body)`,因为在某些情况下,主体可能不需要被释放。我们可以通过使动作必须 `write()` 回应并因此能够`free`它所做的任何分配来结构化API,但这将使得添加一些功能变得不可能,比如支持中间件。
44+
45+
最佳和最简单的方法是使用 `ArenaAllocator` 。其工作原理很简单:当我们`deinit`时,所有分配都被释放。
46+
47+
```zig
48+
fn run(worker: *Worker) void {
49+
const allocator = worker.server.allocator;
50+
while (queue.pop()) |conn| {
51+
var arena = std.heap.ArenaAllocator.init(allocator);
52+
defer arena.deinit();
53+
const action = worker.route(conn.req.url);
54+
action(arena.allocator(), conn.req, conn.res) catch { // TODO: 500 };
55+
worker.write(conn.res);
56+
}
57+
}
58+
```
59+
60+
`std.mem.Allocator` 是一个 "[接口](https://www.openmymind.net/Zig-Interfaces/)" ,我们的动作无需更改。 `ArenaAllocator` 对HTTP服务器来说是一个很好的选择,因为它们与请求绑定,具有明确/可理解的生命周期,并且相对短暂。虽然有可能滥用它们,但可以说:使用更多!
61+
62+
我们可以更进一步并重用相同的Arena。这可能看起来不太有用,但是请看:
63+
64+
```zig
65+
fn run(worker: *Worker) void {
66+
const allocator = worker.server.allocator;
67+
var arena = std.heap.ArenaAllocator.init(allocator);
68+
defer arena.deinit();
69+
while (queue.pop()) |conn| {
70+
// 魔法在此处!
71+
defer _ = arena.reset(.{.retain_with_limit = 8192});
72+
const action = worker.route(conn.req.url);
73+
action(arena.allocator(), conn.req, conn.res) catch { // TODO: 500 };
74+
worker.write(conn.res);
75+
}
76+
}
77+
```
78+
79+
我们将Arena移出了循环,但重要的部分在内部:每个请求后,我们重置了Arena并保留最多8K内存。这意味着对于许多请求,我们无需访问底层分配器(`worker.server.allocator`)。这种方法简化了内存管理。
80+
81+
现在想象一下,如果我们不能用 `retain_with_limit` 重置 Arena,我们还能进行同样的优化吗?可以,我们可以创建自己的分配器,首先尝试使用固定缓冲区分配器(FixedBufferAllocator),如果分配适配,回退到 Arena 分配器。
82+
83+
这里是 `FallbackAllocator` 的完整示例:
84+
85+
```zig
86+
const FallbackAllocator = struct {
87+
primary: Allocator,
88+
fallback: Allocator,
89+
fba: *std.heap.FixedBufferAllocator,
90+
91+
pub fn allocator(self: *FallbackAllocator) Allocator {
92+
return .{
93+
.ptr = self,
94+
.vtable = &.{.alloc = alloc, .resize = resize, .free = free},
95+
};
96+
}
97+
98+
fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, ra: usize) ?[*]u8 {
99+
const self: *FallbackAllocator = @ptrCast(@alignCast(ctx));
100+
return self.primary.rawAlloc(len, ptr_align, ra)
101+
orelse self.fallback.rawAlloc(len, ptr_align, ra);
102+
}
103+
104+
fn resize(ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ra: usize) bool {
105+
const self: *FallbackAllocator = @ptrCast(@alignCast(ctx));
106+
if (self.fba.ownsPtr(buf.ptr)) {
107+
if (self.primary.rawResize(buf, buf_align, new_len, ra)) {
108+
return true;
109+
}
110+
}
111+
return self.fallback.rawResize(buf, buf_align, new_len, ra);
112+
}
113+
114+
fn free(_: *anyopaque, _: []u8, _: u8, _: usize) void {
115+
// we noop this since, in our specific case, we know
116+
// the fallback is an arena, which won't free individual items
117+
}
118+
};
119+
```
120+
121+
我们的`alloc`实现首先尝试使用我们定义的"主"分配器进行分配。如果失败,我们会使用"备用"分配器。作为`std.mem.Allocator`接口的一部分,我们需要实现的`resize`方法会确定正在尝试扩展内存的所有者,并然后调用其`rawResize`方法。为了保持代码简单,我在这里省略了`free`方法的具体实现——在这种特定情况下是可以接受的,因为我们计划使用"主"分配器作为`FixedBufferAllocator`,而"备用"分配器则会是`ArenaAllocator`(因此所有释放操作会在arena的`deinit``reset`时进行)。
122+
123+
接下来我们需要改变我们的`run`方法以利用这个新的分配器:
124+
125+
```zig
126+
fn run(worker: *Worker) void {
127+
const allocator = worker.server.allocator; // 这是FixedBufferAllocator底层的内存
128+
const buf = try allocator.alloc(u8, 8192); // 分配8K字节的内存用于存储数据
129+
defer allocator.free(buf); // 完成后释放内存
130+
131+
var fba = std.heap.FixedBufferAllocator.init(buf); // 初始化FixedBufferAllocator
132+
133+
while (queue.pop()) |conn| {
134+
defer fba.reset(); // 重置FixedBufferAllocator,准备处理下一个请求
135+
136+
var arena = std.heap.ArenaAllocator.init(allocator); // 初始化ArenaAllocator用于分配额外内存
137+
defer arena.deinit();
138+
139+
var fallback = FallbackAllocator{
140+
.fba = &fba,
141+
.primary = fba.allocator(),
142+
.fallback = arena.allocator(),
143+
}; // 创建FallbackAllocator,包含FixedBufferAllocator和ArenaAllocator
144+
145+
const action = worker.route(conn.req.url); // 路由请求到对应的动作处理函数
146+
action(fallback.allocator(), conn.req, conn.res) catch { // 处理动作执行中的错误 };
147+
148+
worker.write(conn.res); // 写回响应信息给客户端
149+
}
150+
}
151+
```
152+
153+
这种方法实现了类似于在`retain_with_limit`中重置arena的功能。我们创建了一个可以重复使用的`FixedBufferAllocator`,用于处理每个请求的8K字节内存需求。由于一个动作可能需要更多的内存,我们仍然需要`ArenaAllocator`来提供额外的空间。通过将`FixedBufferAllocator``ArenaAllocator`包裹在我们的`FallbackAllocator`中,我们可以确保任何分配都首先尝试使用(非常快的)`FixedBufferAllocator`,当其空间用尽时,则会切换到`ArenaAllocator`
154+
155+
我们通过暴露`std.mem.Allocator`接口,可以调整如何工作而不破坏`greet`。这不仅简化了资源管理(例如通过`ArenaAllocator`),而且通过重复使用分配来提高了性能(类似于我们做的`retain_with_limit``FixedBufferAllocator`的操作)。
156+
157+
这个示例应该能突出显示我认为明确的分配器提供的两个实际优势:
158+
1. 简化资源管理(通过类似`ArenaAllocator`的方式)
159+
2. 通过重用分配来提高性能

0 commit comments

Comments
 (0)