【Zig 日报】std.Io.Writer.Allocating ate all my memory

0 views
Skip to first unread message

ZigCC Forum

unread,
Sep 30, 2026, 11:20:38 AM (6 days ago) Sep 30
to zi...@googlegroups.com

原文链接:https://github.com/zigcc/forum/issues/368

如果我告诉你这段代码会打印 1665:

var b: std.ArrayList(u8) = try .initCapacity(init.gpa, 1024);
try b.appendSlice(init.gpa, "a" ** 1025);
std.debug.print("{d}\n", .{b.capacity});

你能猜到下面这段代码会打印什么吗?

var w: Io.Writer.Allocating = try .initCapacity(init.gpa, 1024);
try w.writer.writeAll("a" ** 1025);
std.debug.print("{d}\n", .{w.writer.buffer.len});

和你一样,看到输出是 3204 时你可能会感到惊讶。更奇怪的是,如果你把写入操作拆开,会得到一个更合理的数字 1668:

var w: Io.Writer.Allocating = try .initCapacity(init.gpa, 1024);
try w.writer.writeAll("a" ** 1024);
try w.writer.writeAll("a");
std.debug.print("{d}\n", .{w.writer.buffer.len});

这到底是怎么回事?这似乎是 std.Io.Writer.Allocating 的 drain(排空/清空)实现中的一个 Bug。drain 是 Writer 唯一必须实现的的方法,除了 self 之外,它接收两个参数:

fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize

它接收一个要写入的值列表(以支持向量化 I/O)和一个“splat”计数,即 data 中最后一个值应该被重复写入的次数。我相信 splat 对于压缩特别有用。以下是 zstd/Decompress.zig 中相关的一行代码:

try w.splatByteAll(d.literal_streams.one[0], len);

在这里,writer.splatByteAll 最终会调用 drain。所以我们对 drain 的参数有了一些了解,但为什么它会增长得如此之多?以下是 Allocating 的 drain 函数的简化版本:

fn drain(self: *Allocating, data: []const []const u8, splat: usize) !usize {
  const pattern = data[data.len - 1];
  const splat_len = pattern.len * splat;
  const start_len = self.writer.end;

  for (data) |bytes| {
    try self.ensureUnusedCapacity(bytes.len + splat_len + 1);
    @memcpy(self.writer.buffer[self.writer.end..][0..bytes.len], bytes);
    self.writer.end += bytes.len;
  }

  // ...
}

你能发现问题所在吗?我没看出来,但 Claude 看出来了。对于 data.len == 1 且 splat == 1 的常见情况,我们实际上预留了 2 倍的内存:一次是为数据本身,另一次是为 splat_len(代码中称之为 pattern,本质上又是那份数据)。如果我们调用 drain(&.{"hello", " "}, 100),代码需要为 “hello” 预留 5 字节的空间,并为 pattern 预留 100 字节的空间(" ".len * 100)。但现在的实现为每一个值都预留了 splat 空间,包括 pattern 本身。

ArrayList 不存在这个问题:它没有 splat。如果你的使用场景很简单,只是单纯地追加字节,你可能还是继续使用它为好。

https://www.openmymind.net/std-io-writer-allocating-ate-my-memory/

加入我们

Zig 中文社区是一个开放的组织,我们致力于推广 Zig 在中文群体中的使用,有多种方式可以参与进来: 1. 供稿,分享自己使用 Zig 的心得 2. 改进 ZigCC 组织下的开源项目 3. 加入微信群、QQ 群、QQ 频道、Telegram 群组、Google Groups 与更多 Zig 爱好者交流

Reply all
Reply to author
Forward
0 new messages