Skip to content

Endpoint conns default emits a multi‑hundred‑MB zero blob into the binary - #1

Merged
schphe merged 2 commits into
zigsel:mainfrom
Daeren:patch-1
Jul 29, 2026
Merged

Endpoint conns default emits a multi‑hundred‑MB zero blob into the binary#1
schphe merged 2 commits into
zigsel:mainfrom
Daeren:patch-1

Conversation

@Daeren

@Daeren Daeren commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Description

Endpoint(cfg) declares:

conns: [max]SessionT = [_]SessionT{.{}} ** max,

With game-sized limits (e.g. max_connections = 64, channel_cap = 256, max_payload ≈ 1200, fragmentation on), each Session is on the order of ~2 MB. Zig materializes the field default as a constant, so the linked executable’s .rdata contains roughly:

max_connections × sizeof(Session)

of zeros (often ~150–200 MB), while .text stays well under 1 MB.

This is unrelated to optimize mode (Debug / ReleaseSafe / ReleaseFast all bloat). Runtime still needs a large Endpoint in memory by design (zero steady-state allocation); the bug is that the same table is also baked into the on-disk binary via the array default.

Live slots are already initialized in findOrAdd:

self.conns[i] = .{};
self.conns[i].setup();

Unused slots are never read (used[]). The [_]SessionT{.{}} ** max default is redundant for correctness and only hurts binary size.

Minimal reproduction

const std = @import("std");
const magnet = @import("magnet");

const Schema = magnet.proto.channels(.{
    .a = .{ .mode = .reliable_ordered, .Message = void },
    .b = .{ .mode = .unreliable, .Message = void },
    .c = .{ .mode = .reliable_ordered, .Message = void },
});

const Cfg = magnet.Config{
    .channels = Schema,
    .limits = .{
        .max_connections = 64,
        .channel_cap = 256,
        .max_payload = 1200,
    },
    .delivery = .{
        .fragmentation = true,
        .max_fragments = 64,
    },
    .mtu = 1400,
    .max_datagram = 1400,
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const ep = try magnet.open(Cfg, gpa.allocator());
    defer magnet.close(Cfg, gpa.allocator(), ep);
    std.debug.print("Endpoint sizeof = {d} bytes\n", .{@sizeOf(magnet.Endpoint(Cfg))});
}

Build with zig build -Doptimize=ReleaseFast (or any optimize mode) and inspect the exe:

  • file size ≈ 100–200+ MB
  • .rdata ≈ that size, ~99% zero bytes
  • .text remains small

(Exact size depends on Config; the pattern appears as soon as sizeof(Session) × max_connections is large.)

Solution

In src/proto/endpoint.zig, change the conns field default:

// before
conns: [max]SessionT = [_]SessionT{.{}} ** max,

// after
conns: [max]SessionT = undefined,

No other call-site changes are required: findOrAdd / connectTo already assign conns[i] = .{} and call setup() before use. Endpoint{} / magnet.open leave idle slots uninitialized, which is fine while used[i] == false.

*Same pattern on Server.baselines, much smaller; optional follow-up (baselines: [max_clients]Inner = undefined)

Daeren and others added 2 commits July 27, 2026 21:48
Co-authored-by: Daeren <Daeren@users.noreply.github.com>
@schphe

schphe commented Jul 29, 2026

Copy link
Copy Markdown
Member

good catch, thanks for the fix!

@schphe
schphe merged commit e90cbca into zigsel:main Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants