authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-05-26 14:50:50-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-05-27 20:56:48-07:00
log7fe72d560d94fc379165ab46c9e568e6e684aa41
treedc18983c81629195bc2cd2fca816dc7dcaa12f33
parentd3b03ed64b9c3a4a963e6e5bef536c61c0e3e3c0

std.Progress: move global preallocations to thread memory

Instead of making static buffers configurable, let's pick strong defaults and then use the update thread's stack memory to store the preallocations. The thread uses a fairly shallow stack so this memory is otherwise unused. This also makes the data section of the executable smaller since it runtime allocates the memory when a `std.Progress` instance is allocated, and in the case that the process is not connected to a terminal, it never allocates the memory.

1 files changed, 60 insertions(+), 45 deletions(-)

lib/std/Progress.zig+60-45
......@@ -280,7 +280,6 @@ var global_progress: Progress = .{
280280 .draw_buffer = undefined,
281281 .done = false,
282282
283 // TODO: make these configurable and avoid including the globals in .data if unused
284283 .node_parents = &node_parents_buffer,
285284 .node_storage = &node_storage_buffer,
286285 .node_freelist = &node_freelist_buffer,
......@@ -288,10 +287,10 @@ var global_progress: Progress = .{
288287 .node_end_index = 0,
289288};
290289
291const default_node_storage_buffer_len = 200;
292var node_parents_buffer: [default_node_storage_buffer_len]Node.Parent = undefined;
293var node_storage_buffer: [default_node_storage_buffer_len]Node.Storage = undefined;
294var node_freelist_buffer: [default_node_storage_buffer_len]Node.OptionalIndex = undefined;
290const node_storage_buffer_len = 200;
291var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined;
292var node_storage_buffer: [node_storage_buffer_len]Node.Storage = undefined;
293var node_freelist_buffer: [node_storage_buffer_len]Node.OptionalIndex = undefined;
295294
296295var default_draw_buffer: [4096]u8 = undefined;
297296
......@@ -391,14 +390,21 @@ fn wait(timeout_ns: u64) bool {
391390}
392391
393392fn updateThreadRun() void {
393 // Store this data in the thread so that it does not need to be part of the
394 // linker data of the main executable.
395 var serialized_buffer: Serialized.Buffer = undefined;
396
394397 {
395398 const resize_flag = wait(global_progress.initial_delay_ns);
396399 maybeUpdateSize(resize_flag);
397400
398 if (@atomicLoad(bool, &global_progress.done, .seq_cst))
401 if (@atomicLoad(bool, &global_progress.done, .seq_cst)) {
402 stderr_mutex.lock();
403 defer stderr_mutex.unlock();
399404 return clearTerminal();
405 }
400406
401 const buffer = computeRedraw();
407 const buffer = computeRedraw(&serialized_buffer);
402408 if (stderr_mutex.tryLock()) {
403409 defer stderr_mutex.unlock();
404410 write(buffer);
......@@ -409,10 +415,13 @@ fn updateThreadRun() void {
409415 const resize_flag = wait(global_progress.refresh_rate_ns);
410416 maybeUpdateSize(resize_flag);
411417
412 if (@atomicLoad(bool, &global_progress.done, .seq_cst))
418 if (@atomicLoad(bool, &global_progress.done, .seq_cst)) {
419 stderr_mutex.lock();
420 defer stderr_mutex.unlock();
413421 return clearTerminal();
422 }
414423
415 const buffer = computeRedraw();
424 const buffer = computeRedraw(&serialized_buffer);
416425 if (stderr_mutex.tryLock()) {
417426 defer stderr_mutex.unlock();
418427 write(buffer);
......@@ -433,13 +442,17 @@ pub fn unlockStdErr() void {
433442}
434443
435444fn ipcThreadRun(fd: posix.fd_t) anyerror!void {
445 // Store this data in the thread so that it does not need to be part of the
446 // linker data of the main executable.
447 var serialized_buffer: Serialized.Buffer = undefined;
448
436449 {
437450 _ = wait(global_progress.initial_delay_ns);
438451
439452 if (@atomicLoad(bool, &global_progress.done, .seq_cst))
440453 return;
441454
442 const serialized = serialize();
455 const serialized = serialize(&serialized_buffer);
443456 writeIpc(fd, serialized) catch |err| switch (err) {
444457 error.BrokenPipe => return,
445458 };
......@@ -451,7 +464,7 @@ fn ipcThreadRun(fd: posix.fd_t) anyerror!void {
451464 if (@atomicLoad(bool, &global_progress.done, .seq_cst))
452465 return clearTerminal();
453466
454 const serialized = serialize();
467 const serialized = serialize(&serialized_buffer);
455468 writeIpc(fd, serialized) catch |err| switch (err) {
456469 error.BrokenPipe => return,
457470 };
......@@ -511,17 +524,18 @@ const Children = struct {
511524 sibling: Node.OptionalIndex,
512525};
513526
514// TODO make this configurable
515var serialized_node_parents_buffer: [default_node_storage_buffer_len]Node.Parent = undefined;
516var serialized_node_storage_buffer: [default_node_storage_buffer_len]Node.Storage = undefined;
517var serialized_node_map_buffer: [default_node_storage_buffer_len]Node.Index = undefined;
518
519527const Serialized = struct {
520528 parents: []Node.Parent,
521529 storage: []Node.Storage,
530
531 const Buffer = struct {
532 parents: [node_storage_buffer_len]Node.Parent,
533 storage: [node_storage_buffer_len]Node.Storage,
534 map: [node_storage_buffer_len]Node.Index,
535 };
522536};
523537
524fn serialize() Serialized {
538fn serialize(serialized_buffer: *Serialized.Buffer) Serialized {
525539 var serialized_len: usize = 0;
526540 var any_ipc = false;
527541
......@@ -533,15 +547,15 @@ fn serialize() Serialized {
533547 for (node_parents, node_storage, 0..) |*parent_ptr, *storage_ptr, i| {
534548 var begin_parent = @atomicLoad(Node.Parent, parent_ptr, .seq_cst);
535549 while (begin_parent != .unused) {
536 const dest_storage = &serialized_node_storage_buffer[serialized_len];
550 const dest_storage = &serialized_buffer.storage[serialized_len];
537551 @memcpy(&dest_storage.name, &storage_ptr.name);
538552 dest_storage.completed_count = @atomicLoad(u32, &storage_ptr.completed_count, .monotonic);
539553 dest_storage.estimated_total_count = @atomicLoad(u32, &storage_ptr.estimated_total_count, .monotonic);
540554 const end_parent = @atomicLoad(Node.Parent, parent_ptr, .seq_cst);
541555 if (begin_parent == end_parent) {
542556 any_ipc = any_ipc or (dest_storage.getIpcFd() != null);
543 serialized_node_parents_buffer[serialized_len] = begin_parent;
544 serialized_node_map_buffer[i] = @enumFromInt(serialized_len);
557 serialized_buffer.parents[serialized_len] = begin_parent;
558 serialized_buffer.map[i] = @enumFromInt(serialized_len);
545559 serialized_len += 1;
546560 break;
547561 }
......@@ -551,29 +565,29 @@ fn serialize() Serialized {
551565 }
552566
553567 // Remap parents to point inside serialized arrays.
554 for (serialized_node_parents_buffer[0..serialized_len]) |*parent| {
568 for (serialized_buffer.parents[0..serialized_len]) |*parent| {
555569 parent.* = switch (parent.*) {
556570 .unused => unreachable,
557571 .none => .none,
558 _ => |p| serialized_node_map_buffer[@intFromEnum(p)].toParent(),
572 _ => |p| serialized_buffer.map[@intFromEnum(p)].toParent(),
559573 };
560574 }
561575
562576 // Find nodes which correspond to child processes.
563577 if (any_ipc)
564 serialized_len = serializeIpc(serialized_len);
578 serialized_len = serializeIpc(serialized_len, serialized_buffer);
565579
566580 return .{
567 .parents = serialized_node_parents_buffer[0..serialized_len],
568 .storage = serialized_node_storage_buffer[0..serialized_len],
581 .parents = serialized_buffer.parents[0..serialized_len],
582 .storage = serialized_buffer.storage[0..serialized_len],
569583 };
570584}
571585
572var parents_copy: [default_node_storage_buffer_len]Node.Parent = undefined;
573var storage_copy: [default_node_storage_buffer_len]Node.Storage = undefined;
574var ipc_metadata_copy: [default_node_storage_buffer_len]SavedMetadata = undefined;
586var parents_copy: [node_storage_buffer_len]Node.Parent = undefined;
587var storage_copy: [node_storage_buffer_len]Node.Storage = undefined;
588var ipc_metadata_copy: [node_storage_buffer_len]SavedMetadata = undefined;
575589
576var ipc_metadata: [default_node_storage_buffer_len]SavedMetadata = undefined;
590var ipc_metadata: [node_storage_buffer_len]SavedMetadata = undefined;
577591var ipc_metadata_len: u16 = 0;
578592
579593const SavedMetadata = struct {
......@@ -597,7 +611,7 @@ const SavedMetadata = struct {
597611 }
598612};
599613
600fn serializeIpc(start_serialized_len: usize) usize {
614fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buffer) usize {
601615 var serialized_len = start_serialized_len;
602616 var pipe_buf: [2 * 4096]u8 align(4) = undefined;
603617
......@@ -605,8 +619,8 @@ fn serializeIpc(start_serialized_len: usize) usize {
605619 ipc_metadata_len = 0;
606620
607621 main_loop: for (
608 serialized_node_parents_buffer[0..serialized_len],
609 serialized_node_storage_buffer[0..serialized_len],
622 serialized_buffer.parents[0..serialized_len],
623 serialized_buffer.storage[0..serialized_len],
610624 0..,
611625 ) |main_parent, *main_storage, main_index| {
612626 if (main_parent == .unused) continue;
......@@ -628,7 +642,7 @@ fn serializeIpc(start_serialized_len: usize) usize {
628642 // Ignore all but the last message on the pipe.
629643 var input: []align(2) u8 = pipe_buf[0..bytes_read];
630644 if (input.len == 0) {
631 serialized_len = useSavedIpcData(serialized_len, main_storage, main_index, old_ipc_metadata);
645 serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, old_ipc_metadata);
632646 continue;
633647 }
634648
......@@ -636,7 +650,7 @@ fn serializeIpc(start_serialized_len: usize) usize {
636650 if (input.len < 4) {
637651 std.log.warn("short read: {d} out of 4 header bytes", .{input.len});
638652 // TODO keep track of the short read to trash odd bytes with the next read
639 serialized_len = useSavedIpcData(serialized_len, main_storage, main_index, old_ipc_metadata);
653 serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, old_ipc_metadata);
640654 continue :main_loop;
641655 }
642656 const subtree_len = std.mem.readInt(u32, input[0..4], .little);
......@@ -644,7 +658,7 @@ fn serializeIpc(start_serialized_len: usize) usize {
644658 if (input.len < expected_bytes) {
645659 std.log.warn("short read: {d} out of {d} ({d} nodes)", .{ input.len, expected_bytes, subtree_len });
646660 // TODO keep track of the short read to trash odd bytes with the next read
647 serialized_len = useSavedIpcData(serialized_len, main_storage, main_index, old_ipc_metadata);
661 serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, old_ipc_metadata);
648662 continue :main_loop;
649663 }
650664 if (input.len > expected_bytes) {
......@@ -672,12 +686,12 @@ fn serializeIpc(start_serialized_len: usize) usize {
672686 copyRoot(main_storage, &storage[0]);
673687
674688 // Copy the rest of the tree to the end.
675 @memcpy(serialized_node_storage_buffer[serialized_len..][0 .. storage.len - 1], storage[1..]);
689 @memcpy(serialized_buffer.storage[serialized_len..][0 .. storage.len - 1], storage[1..]);
676690
677691 // Patch up parent pointers taking into account how the subtree is mounted.
678 serialized_node_parents_buffer[serialized_len] = .none;
692 serialized_buffer.parents[serialized_len] = .none;
679693
680 for (serialized_node_parents_buffer[serialized_len..][0 .. parents.len - 1], parents[1..]) |*dest, p| {
694 for (serialized_buffer.parents[serialized_len..][0 .. parents.len - 1], parents[1..]) |*dest, p| {
681695 dest.* = switch (p) {
682696 // Fix bad data so the rest of the code does not see `unused`.
683697 .none, .unused => .none,
......@@ -693,8 +707,8 @@ fn serializeIpc(start_serialized_len: usize) usize {
693707 }
694708
695709 // Save a copy in case any pipes are empty on the next update.
696 @memcpy(parents_copy[0..serialized_len], serialized_node_parents_buffer[0..serialized_len]);
697 @memcpy(storage_copy[0..serialized_len], serialized_node_storage_buffer[0..serialized_len]);
710 @memcpy(parents_copy[0..serialized_len], serialized_buffer.parents[0..serialized_len]);
711 @memcpy(storage_copy[0..serialized_len], serialized_buffer.storage[0..serialized_len]);
698712 @memcpy(ipc_metadata_copy[0..ipc_metadata_len], ipc_metadata[0..ipc_metadata_len]);
699713
700714 return serialized_len;
......@@ -718,6 +732,7 @@ fn findOld(ipc_fd: posix.fd_t, old_metadata: []const SavedMetadata) ?*const Save
718732
719733fn useSavedIpcData(
720734 start_serialized_len: usize,
735 serialized_buffer: *Serialized.Buffer,
721736 main_storage: *Node.Storage,
722737 main_index: usize,
723738 old_metadata: []const SavedMetadata,
......@@ -746,9 +761,9 @@ fn useSavedIpcData(
746761
747762 copyRoot(main_storage, &storage_copy[old_main_index]);
748763
749 @memcpy(serialized_node_storage_buffer[start_serialized_len..][0..storage.len], storage);
764 @memcpy(serialized_buffer.storage[start_serialized_len..][0..storage.len], storage);
750765
751 for (serialized_node_parents_buffer[start_serialized_len..][0..parents.len], parents) |*dest, p| {
766 for (serialized_buffer.parents[start_serialized_len..][0..parents.len], parents) |*dest, p| {
752767 dest.* = switch (p) {
753768 .none, .unused => .none,
754769 _ => |prev| @enumFromInt(if (@intFromEnum(prev) == old_main_index)
......@@ -761,14 +776,14 @@ fn useSavedIpcData(
761776 return start_serialized_len + storage.len;
762777}
763778
764fn computeRedraw() []u8 {
765 const serialized = serialize();
779fn computeRedraw(serialized_buffer: *Serialized.Buffer) []u8 {
780 const serialized = serialize(serialized_buffer);
766781
767782 // Now we can analyze our copy of the graph without atomics, reconstructing
768783 // children lists which do not exist in the canonical data. These are
769784 // needed for tree traversal below.
770785
771 var children_buffer: [default_node_storage_buffer_len]Children = undefined;
786 var children_buffer: [node_storage_buffer_len]Children = undefined;
772787 const children = children_buffer[0..serialized.parents.len];
773788
774789 @memset(children, .{ .child = .none, .sibling = .none });