miller/scripts/perf/multilanguage-timings/optimized/cutzig.zig
John Kerl b7ee4500af
Language-timings reorg (#2000)
* reorg

* naive

* optimizing

* .gitignore

* more
2026-03-01 18:13:58 -05:00

260 lines
8.6 KiB
Zig
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// CLI: step (1-6) arg1, comma-separated field names arg2, filenames arg3+.
// Step: 1=read, 2=+parse, 3=+select, 4=+build, 5=+newline, 6=+write.
const std = @import("std");
const pipeline_cap = 64;
const LineJob = struct { index: usize, line: []const u8 };
const OutJob = struct { index: usize, out: []const u8 };
fn BoundedQueue(comptime T: type) type {
return struct {
list: std.ArrayList(T),
allocator: std.mem.Allocator,
cap: usize,
mutex: std.Thread.Mutex = .{},
condition: std.Thread.Condition = .{},
closed: bool = false,
const Self = @This();
fn init(allocator: std.mem.Allocator, capacity: usize) !Self {
return .{
.list = try std.ArrayList(T).initCapacity(allocator, capacity),
.allocator = allocator,
.cap = capacity,
};
}
fn deinit(self: *Self) void {
self.list.deinit(self.allocator);
}
fn push(self: *Self, item: T) bool {
self.mutex.lock();
defer self.mutex.unlock();
while (self.list.items.len >= self.cap and !self.closed) {
self.condition.wait(&self.mutex);
}
if (self.closed) return false;
self.list.append(self.allocator, item) catch return false;
self.condition.signal();
return true;
}
fn pop(self: *Self) ?T {
self.mutex.lock();
defer self.mutex.unlock();
while (self.list.items.len == 0 and !self.closed) {
self.condition.wait(&self.mutex);
}
if (self.list.items.len == 0) return null;
const item = self.list.orderedRemove(0);
self.condition.signal();
return item;
}
fn close(self: *Self) void {
self.mutex.lock();
defer self.mutex.unlock();
self.closed = true;
self.condition.broadcast();
}
};
}
fn readerRun(allocator: std.mem.Allocator, file: std.fs.File, read_queue: *BoundedQueue(LineJob)) void {
var buf: [256 * 1024]u8 = undefined;
var line_buf = std.ArrayList(u8).initCapacity(allocator, 4096) catch return;
defer line_buf.deinit(allocator);
var index: usize = 0;
while (true) {
const n = file.read(&buf) catch break;
if (n == 0) break;
var i: usize = 0;
while (i < n) {
if (buf[i] == '\n') {
i += 1;
const line = allocator.dupe(u8, line_buf.items) catch break;
_ = read_queue.push(.{ .index = index, .line = line });
index += 1;
line_buf.clearRetainingCapacity();
} else {
line_buf.append(allocator, buf[i]) catch break;
i += 1;
}
}
}
read_queue.close();
}
fn processorRun(
allocator: std.mem.Allocator,
step: u8,
include_fields: []const []const u8,
read_queue: *BoundedQueue(LineJob),
write_queue: *BoundedQueue(OutJob),
) void {
var mymap = std.StringHashMap([]const u8).init(allocator);
defer {
var it = mymap.iterator();
while (it.next()) |e| {
allocator.free(e.key_ptr.*);
allocator.free(e.value_ptr.*);
}
mymap.deinit();
}
var newmap = std.StringHashMap([]const u8).init(allocator);
defer {
var it = newmap.iterator();
while (it.next()) |e| {
allocator.free(e.value_ptr.*);
}
newmap.deinit();
}
var out = std.ArrayList(u8).initCapacity(allocator, 256) catch return;
defer out.deinit(allocator);
while (read_queue.pop()) |job| {
defer allocator.free(job.line);
if (step <= 1) continue;
{
var it = mymap.iterator();
while (it.next()) |e| {
allocator.free(e.key_ptr.*);
allocator.free(e.value_ptr.*);
}
mymap.clearRetainingCapacity();
}
var iter = std.mem.splitScalar(u8, job.line, ',');
while (iter.next()) |field| {
var kv_iter = std.mem.splitScalar(u8, field, '=');
const k = kv_iter.next() orelse continue;
const v = kv_iter.rest();
const k_dup = allocator.dupe(u8, k) catch continue;
const v_dup = allocator.dupe(u8, v) catch {
allocator.free(k_dup);
continue;
};
if (mymap.getPtr(k_dup)) |existing| {
allocator.free(existing.*);
}
mymap.put(k_dup, v_dup) catch {
allocator.free(k_dup);
allocator.free(v_dup);
};
}
if (step <= 2) continue;
{
var it = newmap.iterator();
while (it.next()) |e| {
allocator.free(e.value_ptr.*);
}
newmap.clearRetainingCapacity();
}
for (include_fields) |inc_k| {
if (mymap.get(inc_k)) |val| {
const val_dup = allocator.dupe(u8, val) catch continue;
newmap.put(inc_k, val_dup) catch allocator.free(val_dup);
}
}
if (step <= 3) continue;
out.clearRetainingCapacity();
var first = true;
for (include_fields) |k| {
if (newmap.get(k)) |v| {
if (!first) out.appendSlice(allocator, ",") catch {};
out.appendSlice(allocator, k) catch {};
out.appendSlice(allocator, "=") catch {};
out.appendSlice(allocator, v) catch {};
first = false;
}
}
out.append(allocator, '\n') catch {};
if (step <= 5) continue;
const out_slice = allocator.dupe(u8, out.items) catch continue;
_ = write_queue.push(.{ .index = job.index, .out = out_slice });
}
write_queue.close();
}
fn writerRun(allocator: std.mem.Allocator, write_queue: *BoundedQueue(OutJob)) void {
const stdout_file = std.fs.File.stdout();
while (write_queue.pop()) |job| {
defer allocator.free(job.out);
stdout_file.writeAll(job.out) catch return;
}
}
fn handle(
allocator: std.mem.Allocator,
filename: []const u8,
step: u8,
include_fields: []const []const u8,
) !bool {
var file = if (std.mem.eql(u8, filename, "-"))
std.fs.File.stdin()
else
std.fs.cwd().openFile(filename, .{}) catch |err| {
std.debug.print("open {s}: {}\n", .{ filename, err });
return false;
};
defer if (!std.mem.eql(u8, filename, "-")) file.close();
var read_queue = try BoundedQueue(LineJob).init(allocator, pipeline_cap);
defer read_queue.deinit();
var write_queue = try BoundedQueue(OutJob).init(allocator, pipeline_cap);
defer write_queue.deinit();
const h_reader = try std.Thread.spawn(.{}, readerRun, .{ allocator, file, &read_queue });
const h_processor = try std.Thread.spawn(.{}, processorRun, .{
allocator,
step,
include_fields,
&read_queue,
&write_queue,
});
const h_writer = try std.Thread.spawn(.{}, writerRun, .{ allocator, &write_queue });
h_reader.join();
h_processor.join();
h_writer.join();
return true;
}
pub fn main() !void {
// GPA is slow (debug allocator); page_allocator is ~1020x faster for allocation-heavy workloads.
const allocator = std.heap.page_allocator;
var args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);
if (args.len < 3) {
std.debug.print("usage: {s} <step 1-6> <field1,field2,...> [file ...]\n", .{args[0]});
std.process.exit(1);
}
const step_n = std.fmt.parseInt(u8, args[1], 10) catch {
std.debug.print("step must be 1-6, got {s}\n", .{args[1]});
std.process.exit(1);
};
if (step_n < 1 or step_n > 6) {
std.debug.print("step must be 1-6, got {s}\n", .{args[1]});
std.process.exit(1);
}
var include_fields = try std.ArrayList([]const u8).initCapacity(allocator, 8);
defer include_fields.deinit();
var field_iter = std.mem.splitScalar(u8, args[2], ',');
while (field_iter.next()) |f| {
if (f.len > 0) include_fields.append(allocator, f) catch {};
}
var ok = true;
if (args.len > 3) {
for (args[3..]) |arg| {
const result = handle(allocator, arg, step_n, include_fields.items) catch false;
if (!result) ok = false;
}
} else {
const result = handle(allocator, "-", step_n, include_fields.items) catch false;
if (!result) ok = false;
}
std.process.exit(if (ok) 0 else 1);
}