also looking at this
Migrate to zig 0.16
#5
13 files
+222
-239
Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.
| @@ -3,11 +3,7 @@ const Regex = .Regex; | ||
| 3 | 3 | const SharedRegex = .SharedRegex; |
| 4 | 4 | const RegexCache = .RegexCache; |
| 5 | 5 | |
| 6 | pub fn main() !void { | |
| 7 | var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true }){}; | |
| 8 | defer _ = gpa.deinit(); | |
| 9 | const allocator = gpa.allocator(); | |
| 10 | ||
| 6 | pub fn main(init: std.process.Init) !void { | |
| 11 | 7 | std.debug.print("\n=== Regex Thread Safety Examples ===\n\n", .{}); |
| 12 | 8 | |
| 13 | 9 | // Example 1: Basic concurrent matching |
| @@ -17,7 +13,7 @@ pub fn main() !void { | ||
| 17 | 13 | std.debug.print("───────────────────────────────────────────────\n", .{}); |
| 18 | 14 | |
| 19 | 15 | // Compile once |
| 20 | var regex = try Regex.compile(allocator, "\\d+"); | |
| 16 | var regex = try Regex.compile(init.gpa, "\\d+"); | |
| 21 | 17 | defer regex.deinit(); |
| 22 | 18 | |
| 23 | 19 | const Worker = struct { |
| @@ -63,7 +59,7 @@ pub fn main() !void { | ||
| 63 | 59 | std.debug.print("Example 2: SharedRegex with reference counting\n", .{}); |
| 64 | 60 | std.debug.print("───────────────────────────────────────────────\n", .{}); |
| 65 | 61 | |
| 66 | var shared = try SharedRegex.init(allocator, "test"); | |
| 62 | var shared = try SharedRegex.init(init.gpa, "test"); | |
| 67 | 63 | defer shared.deinit(); |
| 68 | 64 | |
| 69 | 65 | const Worker = struct { |
| @@ -128,7 +124,7 @@ pub fn main() !void { | ||
| 128 | 124 | var threads: [thread_count]std.Thread = undefined; |
| 129 | 125 | |
| 130 | 126 | for (&threads, 0..) |*thread, i| { |
| 131 | thread.* = try std.Thread.spawn(.{}, Worker.run, .{ allocator, i }); | |
| 127 | thread.* = try std.Thread.spawn(.{}, Worker.run, .{ init.gpa, i }); | |
| 132 | 128 | } |
| 133 | 129 | |
| 134 | 130 | for (threads) |thread| { |
| @@ -144,12 +140,12 @@ pub fn main() !void { | ||
| 144 | 140 | std.debug.print("Example 4: Performance with concurrent matching\n", .{}); |
| 145 | 141 | std.debug.print("───────────────────────────────────────────────\n", .{}); |
| 146 | 142 | |
| 147 | var regex = try Regex.compile(allocator, "test"); | |
| 143 | var regex = try Regex.compile(init.gpa, "test"); | |
| 148 | 144 | defer regex.deinit(); |
| 149 | 145 | |
| 150 | 146 | const Worker = struct { |
| 151 | fn run(r: *const Regex) usize { | |
| 152 | var timer = std.time.Timer.start() catch return 0; | |
| 147 | fn run(r: *const Regex, io: std.Io) i64 { | |
| 148 | const start = std.Io.Timestamp.now(io, .real); | |
| 153 | 149 | |
| 154 | 150 | var count: usize = 0; |
| 155 | 151 | var i: usize = 0; |
| @@ -159,37 +155,40 @@ pub fn main() !void { | ||
| 159 | 155 | } else |_| {} |
| 160 | 156 | } |
| 161 | 157 | |
| 162 | const elapsed_ms = timer.read() / 1_000_000; | |
| 163 | return elapsed_ms; | |
| 158 | const end = std.Io.Timestamp.now(io, .real); | |
| 159 | const elapsed = start.durationTo(end); | |
| 160 | return elapsed.toMilliseconds(); | |
| 164 | 161 | } |
| 165 | 162 | }; |
| 166 | 163 | |
| 167 | 164 | // Sequential baseline |
| 168 | const sequential_time = Worker.run(®ex); | |
| 165 | const sequential_time = Worker.run(®ex, init.io); | |
| 169 | 166 | std.debug.print("Sequential: 10,000 matches in {d}ms\n", .{sequential_time}); |
| 170 | 167 | |
| 171 | 168 | // Parallel execution |
| 172 | 169 | const thread_count = 4; |
| 173 | 170 | var threads: [thread_count]std.Thread = undefined; |
| 174 | var times: [thread_count]usize = undefined; | |
| 171 | var times: [thread_count]i64 = undefined; | |
| 175 | 172 | |
| 176 | 173 | const ParallelWorker = struct { |
| 177 | fn run(r: *const Regex, time_ptr: *usize) void { | |
| 178 | time_ptr.* = Worker.run(r); | |
| 174 | fn run(r: *const Regex, io: std.Io, time_ptr: *i64) void { | |
| 175 | time_ptr.* = Worker.run(r, io); | |
| 179 | 176 | } |
| 180 | 177 | }; |
| 181 | 178 | |
| 182 | var parallel_timer = try std.time.Timer.start(); | |
| 179 | // TODO: there is probably a better/correct-er way to do this with io | |
| 180 | const start = std.Io.Timestamp.now(init.io, .real); | |
| 183 | 181 | |
| 184 | 182 | for (&threads, 0..) |*thread, i| { |
| 185 | thread.* = try std.Thread.spawn(.{}, ParallelWorker.run, .{ ®ex, ×[i] }); | |
| 183 | thread.* = try std.Thread.spawn(.{}, ParallelWorker.run, .{ ®ex, init.io, ×[i] }); | |
| 186 | 184 | } |
| 187 | 185 | |
| 188 | 186 | for (threads) |thread| { |
| 189 | 187 | thread.join(); |
| 190 | 188 | } |
| 191 | 189 | |
| 192 | const total_parallel = parallel_timer.read() / 1_000_000; | |
| 190 | const end = std.Io.Timestamp.now(init.io, .real); | |
| 191 | const total_parallel = start.durationTo(end).toMilliseconds(); | |
| 193 | 192 | |
| 194 | 193 | std.debug.print("Parallel ({d} threads): 40,000 matches in {d}ms\n", .{ thread_count, total_parallel }); |
| 195 | 194 | std.debug.print("Individual thread times: ", .{}); |
| @@ -206,13 +205,13 @@ pub fn main() !void { renamed throughout | ||
| 206 | 205 | std.debug.print("Example 5: Multiple patterns with concurrent access\n", .{}); |
| 207 | 206 | std.debug.print("───────────────────────────────────────────────\n", .{}); |
| 208 | 207 | |
| 209 | var email_regex = try Regex.compile(allocator, "[a-z]+@[a-z]+\\.[a-z]+"); | |
| 208 | var email_regex = try Regex.compile(init.gpa, "[a-z]+@[a-z]+\\.[a-z]+"); | |
| 210 | 209 | defer email_regex.deinit(); |
| 211 | 210 | |
| 212 | var phone_regex = try Regex.compile(allocator, "\\d{3}-\\d{4}"); | |
| 211 | var phone_regex = try Regex.compile(init.gpa, "\\d{3}-\\d{4}"); | |
| 213 | 212 | defer phone_regex.deinit(); |
| 214 | 213 | |
| 215 | var url_regex = try Regex.compile(allocator, "https?://"); | |
| 214 | var url_regex = try Regex.compile(init.gpa, "https?://"); | |
| 216 | 215 | defer url_regex.deinit(); |
| 217 | 216 | |
| 218 | 217 | const Worker = struct { |