From 5acf1f524ac3732136137bd2799fd60c6d0900fc Mon Sep 17 00:00:00 2001 From: Archbirdplus Date: Mon, 3 Jun 2024 15:18:22 -0700 Subject: [PATCH 1/6] threadpool: track and show task times --- src/gui/windows/debug.zig | 7 ++++++- src/utils.zig | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/gui/windows/debug.zig b/src/gui/windows/debug.zig index cc13dfaf..fb83e586 100644 --- a/src/gui/windows/debug.zig +++ b/src/gui/windows/debug.zig @@ -38,6 +38,11 @@ pub fn render() void { y += 8; draw.print("Queue size: {}", .{main.threadPool.queueSize()}, 0, y, 8, .left); y += 8; + const perf = main.threadPool.getPerformance(); + draw.print("Queue task time: {} ms / {} tasks", .{@divFloor(perf.utime, 1000), perf.tasks}, 0, y, 8, .left); + y += 8; + draw.print(" {} µs / task", .{@divFloor(perf.utime, @max(perf.tasks, 1))}, 0, y, 8, .left); + y += 8; draw.print("Mesh Queue size: {}", .{main.renderer.mesh_storage.updatableList.items.len}, 0, y, 8, .left); y += 8; { @@ -57,4 +62,4 @@ pub fn render() void { draw.print("Opaque faces: {}, Transparent faces: {}", .{main.renderer.chunk_meshing.quadsDrawn, main.renderer.chunk_meshing.transparentQuadsDrawn}, 0, y, 8, .left); y += 8; } -} \ No newline at end of file +} diff --git a/src/utils.zig b/src/utils.zig index 0b2ec150..4e04f677 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -1000,6 +1000,11 @@ pub const ThreadPool = struct { run: *const fn(*anyopaque) void, clean: *const fn(*anyopaque) void, }; + pub const Performance = struct { + mutex: std.Thread.Mutex = .{}, + tasks: u32 = 0, + utime: i64 = 0, + }; const refreshTime: u32 = 100; // The time after which all priorities get refreshed in milliseconds. threads: []std.Thread, @@ -1007,11 +1012,14 @@ pub const ThreadPool = struct { loadList: *BlockingMaxHeap(Task), allocator: NeverFailingAllocator, + performance: *Performance, + pub fn init(allocator: NeverFailingAllocator, threadCount: usize) ThreadPool { const self = ThreadPool { .threads = allocator.alloc(std.Thread, threadCount), .currentTasks = allocator.alloc(Atomic(?*const VTable), threadCount), .loadList = BlockingMaxHeap(Task).init(allocator), + .performance = allocator.create(Performance), .allocator = allocator, }; for(self.threads, 0..) |*thread, i| { @@ -1062,6 +1070,16 @@ pub const ThreadPool = struct { } } + pub fn getPerformance(self: ThreadPool) Performance { + self.performance.mutex.lock(); + defer { + self.performance.tasks = 0; + self.performance.utime = 0; + self.performance.mutex.unlock(); + } + return self.performance.*; + } + fn run(self: ThreadPool, id: usize) void { // In case any of the tasks wants to allocate memory: var sta = StackAllocator.init(main.globalAllocator, 1 << 23); @@ -1073,7 +1091,13 @@ pub const ThreadPool = struct { { const task = self.loadList.extractMax() catch break; self.currentTasks[id].store(task.vtable, .monotonic); + const start = std.time.microTimestamp(); task.vtable.run(task.self); + const end = std.time.microTimestamp(); + self.performance.mutex.lock(); + self.performance.tasks += 1; + self.performance.utime += end - start; + self.performance.mutex.unlock(); self.currentTasks[id].store(null, .monotonic); } From ad1aa463dceaada6f79b00bfc8e2e2c7a5353365 Mon Sep 17 00:00:00 2001 From: Archbirdplus Date: Tue, 4 Jun 2024 16:35:30 -0700 Subject: [PATCH 2/6] debug window: subdivide task performance --- src/audio.zig | 2 +- src/gui/windows/debug.zig | 16 +++++++++++++--- src/network.zig | 2 +- src/renderer/chunk_meshing.zig | 4 ++-- src/renderer/mesh_storage.zig | 4 ++-- src/server/world.zig | 6 +++--- src/utils.zig | 33 ++++++++++++++++++++++++++------- 7 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/audio.zig b/src/audio.zig index 4a65178b..51e0a51f 100644 --- a/src/audio.zig +++ b/src/audio.zig @@ -100,7 +100,7 @@ const MusicLoadTask = struct { task.* = MusicLoadTask { .musicId = musicId, }; - main.threadPool.addTask(task, &vtable); + main.threadPool.addTask(task, &vtable, .misc); taskMutex.lock(); defer taskMutex.unlock(); activeTasks.append(main.globalAllocator, musicId); diff --git a/src/gui/windows/debug.zig b/src/gui/windows/debug.zig index fb83e586..0820fe90 100644 --- a/src/gui/windows/debug.zig +++ b/src/gui/windows/debug.zig @@ -5,6 +5,7 @@ const graphics = main.graphics; const draw = graphics.draw; const Texture = graphics.Texture; const Vec2f = main.vec.Vec2f; +const TaskType = main.utils.ThreadPool.TaskType; const gui = @import("../gui.zig"); const GuiWindow = gui.GuiWindow; @@ -39,10 +40,19 @@ pub fn render() void { draw.print("Queue size: {}", .{main.threadPool.queueSize()}, 0, y, 8, .left); y += 8; const perf = main.threadPool.getPerformance(); - draw.print("Queue task time: {} ms / {} tasks", .{@divFloor(perf.utime, 1000), perf.tasks}, 0, y, 8, .left); - y += 8; - draw.print(" {} µs / task", .{@divFloor(perf.utime, @max(perf.tasks, 1))}, 0, y, 8, .left); + const values = comptime std.enums.values(TaskType); + inline for(values) |t| { + const name = switch (t) { + .chunkgen => "chunkgen", + .lighting => "lighting", + .meshgen => "meshing", + .misc => "other", + else => continue, + }; + const i = @intFromEnum(t); + draw.print(" " ++ name ++ " time: {} ms ({} µs/task)", .{@divFloor(perf.utime[i], 1000), @divFloor(perf.utime[i], perf.tasks[i])}, 0, y, 8, .left); y += 8; + } draw.print("Mesh Queue size: {}", .{main.renderer.mesh_storage.updatableList.items.len}, 0, y, 8, .left); y += 8; { diff --git a/src/network.zig b/src/network.zig index b7f981eb..7b77ec36 100644 --- a/src/network.zig +++ b/src/network.zig @@ -1748,7 +1748,7 @@ const ProtocolTask = struct { .protocol = protocol, .data = main.globalAllocator.dupe(u8, data), }; - main.threadPool.addTask(task, &vtable); + main.threadPool.addTask(task, &vtable, .misc); } pub fn getPriority(_: *ProtocolTask) f32 { diff --git a/src/renderer/chunk_meshing.zig b/src/renderer/chunk_meshing.zig index 10a832e6..3efc1bfc 100644 --- a/src/renderer/chunk_meshing.zig +++ b/src/renderer/chunk_meshing.zig @@ -640,7 +640,7 @@ pub const ChunkMesh = struct { task.* = .{ .mesh = mesh, }; - main.threadPool.addTask(task, &vtable); + main.threadPool.addTask(task, &vtable, .lighting); } pub fn getPriority(_: *LightRefreshTask) f32 { @@ -1237,4 +1237,4 @@ pub const ChunkMesh = struct { chunkList.append(self.chunkAllocation.start); transparentQuadsDrawn += self.culledSortingCount; } -}; \ No newline at end of file +}; diff --git a/src/renderer/mesh_storage.zig b/src/renderer/mesh_storage.zig index eaca6596..f4c81aed 100644 --- a/src/renderer/mesh_storage.zig +++ b/src/renderer/mesh_storage.zig @@ -1049,7 +1049,7 @@ pub const MeshGenerationTask = struct { task.* = MeshGenerationTask { .mesh = mesh, }; - main.threadPool.addTask(task, &vtable); + main.threadPool.addTask(task, &vtable, .meshgen); } pub fn getPriority(self: *MeshGenerationTask) f32 { @@ -1092,4 +1092,4 @@ pub fn updateLightMap(map: *LightMap.LightMapFragment) void { mutex.lock(); defer mutex.unlock(); mapUpdatableList.append(map); -} \ No newline at end of file +} diff --git a/src/server/world.zig b/src/server/world.zig index 9ceb9305..39140f64 100644 --- a/src/server/world.zig +++ b/src/server/world.zig @@ -50,7 +50,7 @@ const ChunkManager = struct { .creationTime = std.time.milliTimestamp(), .source = source, }; - main.threadPool.addTask(task, &vtable); + main.threadPool.addTask(task, &vtable, .chunkgen); } pub fn getPriority(self: *ChunkLoadTask) f32 { @@ -114,7 +114,7 @@ const ChunkManager = struct { .creationTime = std.time.milliTimestamp(), .source = source, }; - main.threadPool.addTask(task, &vtable); + main.threadPool.addTask(task, &vtable, .lighting); } pub fn getPriority(self: *LightMapLoadTask) f32 { @@ -431,7 +431,7 @@ pub const ServerWorld = struct { task.* = .{ .pos = pos, }; - main.threadPool.addTask(task, &vtable); + main.threadPool.addTask(task, &vtable, .chunkgen); } pub fn getPriority(_: *RegenerateLODTask) f32 { diff --git a/src/utils.zig b/src/utils.zig index 4e04f677..e1fafa93 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -985,10 +985,18 @@ pub fn BlockingMaxHeap(comptime T: type) type { } pub const ThreadPool = struct { + pub const TaskType = enum(usize) { + chunkgen, + lighting, + meshgen, + misc, + taskTypes, + }; const Task = struct { cachedPriority: f32, self: *anyopaque, vtable: *const VTable, + taskType: TaskType, fn biggerThan(self: Task, other: Task) bool { return self.cachedPriority > other.cachedPriority; @@ -1002,8 +1010,19 @@ pub const ThreadPool = struct { }; pub const Performance = struct { mutex: std.Thread.Mutex = .{}, - tasks: u32 = 0, - utime: i64 = 0, + tasks: [@intFromEnum(TaskType.taskTypes)]u32 = {}, + utime: [@intFromEnum(TaskType.taskTypes)]i64 = {}, + + fn clear(self: *Performance) void { + for(0..@intFromEnum(TaskType.taskTypes)) |i| { + self.tasks[i] = 0; + self.utime[i] = 0; + } + } + + fn init(self: Performance) void { + self.clear(); + } }; const refreshTime: u32 = 100; // The time after which all priorities get refreshed in milliseconds. @@ -1073,8 +1092,7 @@ pub const ThreadPool = struct { pub fn getPerformance(self: ThreadPool) Performance { self.performance.mutex.lock(); defer { - self.performance.tasks = 0; - self.performance.utime = 0; + self.performance.clear(); self.performance.mutex.unlock(); } return self.performance.*; @@ -1095,8 +1113,8 @@ pub const ThreadPool = struct { task.vtable.run(task.self); const end = std.time.microTimestamp(); self.performance.mutex.lock(); - self.performance.tasks += 1; - self.performance.utime += end - start; + self.performance.tasks[@intFromEnum(task.taskType)] += 1; + self.performance.utime[@intFromEnum(task.taskType)] += end - start; self.performance.mutex.unlock(); self.currentTasks[id].store(null, .monotonic); } @@ -1124,11 +1142,12 @@ pub const ThreadPool = struct { } } - pub fn addTask(self: ThreadPool, task: *anyopaque, vtable: *const VTable) void { + pub fn addTask(self: ThreadPool, task: *anyopaque, vtable: *const VTable, taskType: TaskType) void { self.loadList.add(Task { .cachedPriority = vtable.getPriority(task), .vtable = vtable, .self = task, + .taskType = taskType, }); } From af2962b0490d6f53c3ac57a4ac079a29984b27d1 Mon Sep 17 00:00:00 2001 From: Archbirdplus Date: Tue, 4 Jun 2024 16:49:05 -0700 Subject: [PATCH 3/6] indent debug window code --- src/gui/windows/debug.zig | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/gui/windows/debug.zig b/src/gui/windows/debug.zig index 0820fe90..c43bf7ef 100644 --- a/src/gui/windows/debug.zig +++ b/src/gui/windows/debug.zig @@ -39,20 +39,20 @@ pub fn render() void { y += 8; draw.print("Queue size: {}", .{main.threadPool.queueSize()}, 0, y, 8, .left); y += 8; - const perf = main.threadPool.getPerformance(); - const values = comptime std.enums.values(TaskType); - inline for(values) |t| { - const name = switch (t) { - .chunkgen => "chunkgen", - .lighting => "lighting", - .meshgen => "meshing", - .misc => "other", - else => continue, - }; - const i = @intFromEnum(t); - draw.print(" " ++ name ++ " time: {} ms ({} µs/task)", .{@divFloor(perf.utime[i], 1000), @divFloor(perf.utime[i], perf.tasks[i])}, 0, y, 8, .left); + const perf = main.threadPool.getPerformance(); + const values = comptime std.enums.values(TaskType); + inline for(values) |t| { + const name = switch (t) { + .chunkgen => "chunkgen", + .lighting => "lighting", + .meshgen => "meshing", + .misc => "other", + else => continue, + }; + const i = @intFromEnum(t); + draw.print(" " ++ name ++ " time: {} ms ({} µs/task)", .{@divFloor(perf.utime[i], 1000), @divFloor(perf.utime[i], perf.tasks[i])}, 0, y, 8, .left); y += 8; - } + } draw.print("Mesh Queue size: {}", .{main.renderer.mesh_storage.updatableList.items.len}, 0, y, 8, .left); y += 8; { From ae11566915e0b9faba8b1eb7ca36dbaeebf509e6 Mon Sep 17 00:00:00 2001 From: Archbirdplus Date: Tue, 4 Jun 2024 19:44:10 -0700 Subject: [PATCH 4/6] task perfs: remove meshgen for lighting --- src/gui/windows/debug.zig | 1 - src/renderer/mesh_storage.zig | 2 +- src/utils.zig | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/gui/windows/debug.zig b/src/gui/windows/debug.zig index c43bf7ef..3aaccc4b 100644 --- a/src/gui/windows/debug.zig +++ b/src/gui/windows/debug.zig @@ -45,7 +45,6 @@ pub fn render() void { const name = switch (t) { .chunkgen => "chunkgen", .lighting => "lighting", - .meshgen => "meshing", .misc => "other", else => continue, }; diff --git a/src/renderer/mesh_storage.zig b/src/renderer/mesh_storage.zig index f4c81aed..2999dc72 100644 --- a/src/renderer/mesh_storage.zig +++ b/src/renderer/mesh_storage.zig @@ -1049,7 +1049,7 @@ pub const MeshGenerationTask = struct { task.* = MeshGenerationTask { .mesh = mesh, }; - main.threadPool.addTask(task, &vtable, .meshgen); + main.threadPool.addTask(task, &vtable, .lighting); } pub fn getPriority(self: *MeshGenerationTask) f32 { diff --git a/src/utils.zig b/src/utils.zig index e1fafa93..51534319 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -988,7 +988,6 @@ pub const ThreadPool = struct { pub const TaskType = enum(usize) { chunkgen, lighting, - meshgen, misc, taskTypes, }; From ce26e555fbb61b49a14e370c7a7ab2890b77c125 Mon Sep 17 00:00:00 2001 From: Archbirdplus Date: Wed, 5 Jun 2024 12:57:28 -0700 Subject: [PATCH 5/6] task perfs: don't divide by zero --- src/gui/windows/debug.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/windows/debug.zig b/src/gui/windows/debug.zig index 3aaccc4b..67421c11 100644 --- a/src/gui/windows/debug.zig +++ b/src/gui/windows/debug.zig @@ -49,7 +49,7 @@ pub fn render() void { else => continue, }; const i = @intFromEnum(t); - draw.print(" " ++ name ++ " time: {} ms ({} µs/task)", .{@divFloor(perf.utime[i], 1000), @divFloor(perf.utime[i], perf.tasks[i])}, 0, y, 8, .left); + draw.print(" " ++ name ++ " time: {} ms ({} µs/task)", .{@divFloor(perf.utime[i], 1000), @divFloor(perf.utime[i], @max(1, perf.tasks[i]))}, 0, y, 8, .left); y += 8; } draw.print("Mesh Queue size: {}", .{main.renderer.mesh_storage.updatableList.items.len}, 0, y, 8, .left); From 93a9d048aac29468ef6cb0a62316c84591ebc194 Mon Sep 17 00:00:00 2001 From: Archbirdplus Date: Wed, 5 Jun 2024 13:25:22 -0700 Subject: [PATCH 6/6] task perf: move taskType to vtable --- src/audio.zig | 3 ++- src/network.zig | 3 ++- src/renderer/chunk_meshing.zig | 3 ++- src/renderer/mesh_storage.zig | 3 ++- src/server/world.zig | 9 ++++++--- src/utils.zig | 10 +++++----- 6 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/audio.zig b/src/audio.zig index 51e0a51f..b022e679 100644 --- a/src/audio.zig +++ b/src/audio.zig @@ -93,6 +93,7 @@ const MusicLoadTask = struct { .isStillNeeded = @ptrCast(&isStillNeeded), .run = @ptrCast(&run), .clean = @ptrCast(&clean), + .taskType = .misc, }; pub fn schedule(musicId: []const u8) void { @@ -100,7 +101,7 @@ const MusicLoadTask = struct { task.* = MusicLoadTask { .musicId = musicId, }; - main.threadPool.addTask(task, &vtable, .misc); + main.threadPool.addTask(task, &vtable); taskMutex.lock(); defer taskMutex.unlock(); activeTasks.append(main.globalAllocator, musicId); diff --git a/src/network.zig b/src/network.zig index 7b77ec36..6de63863 100644 --- a/src/network.zig +++ b/src/network.zig @@ -1739,6 +1739,7 @@ const ProtocolTask = struct { .isStillNeeded = @ptrCast(&isStillNeeded), .run = @ptrCast(&run), .clean = @ptrCast(&clean), + .taskType = .misc, }; pub fn schedule(conn: *Connection, protocol: u8, data: []const u8) void { @@ -1748,7 +1749,7 @@ const ProtocolTask = struct { .protocol = protocol, .data = main.globalAllocator.dupe(u8, data), }; - main.threadPool.addTask(task, &vtable, .misc); + main.threadPool.addTask(task, &vtable); } pub fn getPriority(_: *ProtocolTask) f32 { diff --git a/src/renderer/chunk_meshing.zig b/src/renderer/chunk_meshing.zig index 3efc1bfc..d75ff8f3 100644 --- a/src/renderer/chunk_meshing.zig +++ b/src/renderer/chunk_meshing.zig @@ -633,6 +633,7 @@ pub const ChunkMesh = struct { .isStillNeeded = @ptrCast(&isStillNeeded), .run = @ptrCast(&run), .clean = @ptrCast(&clean), + .taskType = .lighting, }; pub fn scheduleAndDecreaseRefCount(mesh: *ChunkMesh) void { @@ -640,7 +641,7 @@ pub const ChunkMesh = struct { task.* = .{ .mesh = mesh, }; - main.threadPool.addTask(task, &vtable, .lighting); + main.threadPool.addTask(task, &vtable); } pub fn getPriority(_: *LightRefreshTask) f32 { diff --git a/src/renderer/mesh_storage.zig b/src/renderer/mesh_storage.zig index 2999dc72..23dfc187 100644 --- a/src/renderer/mesh_storage.zig +++ b/src/renderer/mesh_storage.zig @@ -1042,6 +1042,7 @@ pub const MeshGenerationTask = struct { .isStillNeeded = @ptrCast(&isStillNeeded), .run = @ptrCast(&run), .clean = @ptrCast(&clean), + .taskType = .lighting, }; pub fn schedule(mesh: *chunk.Chunk) void { @@ -1049,7 +1050,7 @@ pub const MeshGenerationTask = struct { task.* = MeshGenerationTask { .mesh = mesh, }; - main.threadPool.addTask(task, &vtable, .lighting); + main.threadPool.addTask(task, &vtable); } pub fn getPriority(self: *MeshGenerationTask) f32 { diff --git a/src/server/world.zig b/src/server/world.zig index 39140f64..96d75844 100644 --- a/src/server/world.zig +++ b/src/server/world.zig @@ -41,6 +41,7 @@ const ChunkManager = struct { .isStillNeeded = @ptrCast(&isStillNeeded), .run = @ptrCast(&run), .clean = @ptrCast(&clean), + .taskType = .chunkgen, }; pub fn scheduleAndDecreaseRefCount(pos: ChunkPosition, source: ?*User) void { @@ -50,7 +51,7 @@ const ChunkManager = struct { .creationTime = std.time.milliTimestamp(), .source = source, }; - main.threadPool.addTask(task, &vtable, .chunkgen); + main.threadPool.addTask(task, &vtable); } pub fn getPriority(self: *ChunkLoadTask) f32 { @@ -105,6 +106,7 @@ const ChunkManager = struct { .isStillNeeded = @ptrCast(&isStillNeeded), .run = @ptrCast(&run), .clean = @ptrCast(&clean), + .taskType = .lighting, }; pub fn scheduleAndDecreaseRefCount(pos: terrain.SurfaceMap.MapFragmentPosition, source: ?*User) void { @@ -114,7 +116,7 @@ const ChunkManager = struct { .creationTime = std.time.milliTimestamp(), .source = source, }; - main.threadPool.addTask(task, &vtable, .lighting); + main.threadPool.addTask(task, &vtable); } pub fn getPriority(self: *LightMapLoadTask) f32 { @@ -424,6 +426,7 @@ pub const ServerWorld = struct { .isStillNeeded = @ptrCast(&isStillNeeded), .run = @ptrCast(&run), .clean = @ptrCast(&clean), + .taskType = .chunkgen, }; pub fn schedule(pos: ChunkPosition) void { @@ -431,7 +434,7 @@ pub const ServerWorld = struct { task.* = .{ .pos = pos, }; - main.threadPool.addTask(task, &vtable, .chunkgen); + main.threadPool.addTask(task, &vtable); } pub fn getPriority(_: *RegenerateLODTask) f32 { diff --git a/src/utils.zig b/src/utils.zig index 51534319..61078b6c 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -995,7 +995,6 @@ pub const ThreadPool = struct { cachedPriority: f32, self: *anyopaque, vtable: *const VTable, - taskType: TaskType, fn biggerThan(self: Task, other: Task) bool { return self.cachedPriority > other.cachedPriority; @@ -1006,6 +1005,7 @@ pub const ThreadPool = struct { isStillNeeded: *const fn(*anyopaque, milliTime: i64) bool, run: *const fn(*anyopaque) void, clean: *const fn(*anyopaque) void, + taskType: TaskType = .misc, }; pub const Performance = struct { mutex: std.Thread.Mutex = .{}, @@ -1111,9 +1111,10 @@ pub const ThreadPool = struct { const start = std.time.microTimestamp(); task.vtable.run(task.self); const end = std.time.microTimestamp(); + const taskType = @intFromEnum(task.vtable.taskType); self.performance.mutex.lock(); - self.performance.tasks[@intFromEnum(task.taskType)] += 1; - self.performance.utime[@intFromEnum(task.taskType)] += end - start; + self.performance.tasks[taskType] += 1; + self.performance.utime[taskType] += end - start; self.performance.mutex.unlock(); self.currentTasks[id].store(null, .monotonic); } @@ -1141,12 +1142,11 @@ pub const ThreadPool = struct { } } - pub fn addTask(self: ThreadPool, task: *anyopaque, vtable: *const VTable, taskType: TaskType) void { + pub fn addTask(self: ThreadPool, task: *anyopaque, vtable: *const VTable) void { self.loadList.add(Task { .cachedPriority = vtable.getPriority(task), .vtable = vtable, .self = task, - .taskType = taskType, }); }