Skip to content

Commit 888bb21

Browse files
authored
knucleotide: add parallelized zig version (#473)
1 parent 77f6bd5 commit 888bb21

File tree

2 files changed

+195
-0
lines changed

2 files changed

+195
-0
lines changed

bench/algorithm/knucleotide/1-m.zig

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
const std = @import("std");
2+
3+
const gpa = std.heap.c_allocator;
4+
const stdout = std.io.getStdOut().writer();
5+
6+
const Code = struct {
7+
data: u64,
8+
9+
pub inline fn encodeByte(c: u8) u2 {
10+
return @intCast((c >> 1) & 0b11);
11+
}
12+
13+
pub inline fn makeMask(frame: usize) u64 {
14+
return (@as(u64, 1) << @as(u6, @intCast(2 * frame))) - 1;
15+
}
16+
17+
pub inline fn push(self: *Code, c: u8, mask: u64) void {
18+
self.data = ((self.data << 2) | c) & mask;
19+
}
20+
21+
pub fn fromStr(s: []const u8) Code {
22+
const mask = Code.makeMask(s.len);
23+
var res = Code{ .data = 0 };
24+
for (s) |c| {
25+
res.push(Code.encodeByte(c), mask);
26+
}
27+
return res;
28+
}
29+
30+
pub fn toString(self: Code, frame: usize) ![]const u8 {
31+
var result = std.ArrayList(u8).init(gpa);
32+
var code = self.data;
33+
for (0..frame) |_| {
34+
const c: u8 = switch (@as(u2, @truncate(code))) {
35+
Code.encodeByte('A') => 'A',
36+
Code.encodeByte('T') => 'T',
37+
Code.encodeByte('G') => 'G',
38+
Code.encodeByte('C') => 'C',
39+
};
40+
try result.append(c);
41+
code >>= 2;
42+
}
43+
std.mem.reverse(u8, result.items);
44+
return result.toOwnedSlice();
45+
}
46+
};
47+
48+
pub fn readInput() ![]const u8 {
49+
const args = try std.process.argsAlloc(gpa);
50+
defer std.process.argsFree(gpa, args);
51+
const file_name = if (args.len > 1) args[1] else "25000_in";
52+
const file = try std.fs.cwd().openFile(file_name, .{});
53+
var buffered_reader = std.io.bufferedReader(file.reader());
54+
const reader = buffered_reader.reader();
55+
{ // skip past first lines starting with '>'
56+
var i: u8 = 0;
57+
while (i < 3) : (i += 1) {
58+
while (true) {
59+
const c = try reader.readByte();
60+
if (c == '>') break;
61+
}
62+
}
63+
while (true) {
64+
const c = try reader.readByte();
65+
if (c == '\n') break;
66+
}
67+
}
68+
69+
var buf = try reader.readAllAlloc(gpa, std.math.maxInt(u32));
70+
// In place, remove all newlines from buf and encode nucleotides
71+
// using only the last 2 bits in each byte.
72+
{
73+
var i: usize = 0;
74+
for (buf) |c| {
75+
if (c != '\n') {
76+
// Gives a -> 0x00, c -> 0x01, g -> 0x03, t -> 0x02
77+
buf[i] = (c >> 1) & 0x03;
78+
i += 1;
79+
}
80+
}
81+
buf.len = i;
82+
}
83+
return buf;
84+
}
85+
86+
const CodeContext = struct {
87+
pub fn eql(_: CodeContext, a: Code, b: Code) bool { return a.data == b.data; }
88+
pub fn hash(_: CodeContext, c: Code) u64 { return c.data ^ (c.data >> 7); }
89+
};
90+
91+
const Map = std.HashMapUnmanaged(Code, u32, CodeContext, 45);
92+
93+
fn genMap(taskIndex: usize, from: usize, to: usize, seq: []const u8, frame: usize, maps: []Map) !void {
94+
const map = &maps[taskIndex];
95+
const mask = Code.makeMask(frame);
96+
var code: Code = .{ .data = 0 };
97+
for (seq[from..][0..frame - 1]) |e| code.push(e, mask);
98+
for (seq[frame - 1..][from .. to]) |e| {
99+
code.push(e, mask);
100+
const kv = try map.getOrPutValue(gpa, code, 0);
101+
kv.value_ptr.* += 1;
102+
}
103+
}
104+
105+
const CountCode = struct {
106+
count: u32,
107+
code: Code,
108+
109+
pub fn dsc(_: void, a: CountCode, b: CountCode) bool {
110+
const order = std.math.order(a.count, b.count);
111+
return order == .gt or (order == .eq and b.code.data > a.code.data);
112+
}
113+
};
114+
115+
fn printMap(frame: usize, maps: []const Map) !void {
116+
const code_limit = 16;
117+
var counts: [code_limit]u32 = @splat(0);
118+
var total: u64 = 0;
119+
for (maps) |map| {
120+
var iter = map.iterator();
121+
while (iter.next()) |it| {
122+
const code = it.key_ptr.*;
123+
const count = it.value_ptr.*;
124+
total += count;
125+
counts[code.data] += count;
126+
}
127+
}
128+
129+
var cc: std.BoundedArray(CountCode, code_limit) = .{};
130+
for (counts, 0..) |count, code_data| if (count > 0) {
131+
cc.appendAssumeCapacity(.{ .count = count, .code = .{ .data = @intCast(code_data) } });
132+
};
133+
std.mem.sort(CountCode, cc.slice(), {}, CountCode.dsc);
134+
135+
for (cc.slice()) |c| {
136+
try stdout.print("{!s} {d:.3}\n", .{
137+
c.code.toString(frame),
138+
@as(f32, @floatFromInt(c.count)) / @as(f32, @floatFromInt(total)) * 100.0,
139+
});
140+
}
141+
try stdout.print("\n", .{});
142+
}
143+
144+
fn printOcc(occ: []const u8, maps: []const Map) !void {
145+
const code = Code.fromStr(occ);
146+
var total: u32 = 0;
147+
for (maps) |m| {
148+
if (m.get(code)) |count| total += count;
149+
}
150+
try stdout.print("{}\t{s}\n", .{ total, occ });
151+
}
152+
153+
fn runInParallel(task_count: usize, len: usize, comptime f: anytype, args: anytype) !void {
154+
const tasks = try gpa.alloc(std.Thread, task_count - 1);
155+
defer gpa.free(tasks);
156+
const len_per_task = @divTrunc(len, task_count);
157+
for (tasks, 0..) |*task, i| {
158+
const first = len_per_task * i;
159+
const last = first + len_per_task;
160+
task.* = try std.Thread.spawn(.{}, f, .{ i, first, last } ++ args);
161+
}
162+
try @call(.auto, f, .{ tasks.len, tasks.len * len_per_task, len } ++ args);
163+
for (tasks) |*task| task.join();
164+
}
165+
166+
fn genMaps(seq: []const u8, frame: usize, maps: []Map) !void {
167+
for (maps) |*m| m.clearAndFree(gpa);
168+
try runInParallel(maps.len, seq.len - (frame - 1), genMap, .{seq, frame, maps});
169+
}
170+
171+
pub fn main() !void {
172+
const seq = try readInput();
173+
const task_count = try std.Thread.getCpuCount();
174+
const maps = try gpa.alloc(Map, task_count);
175+
defer gpa.free(maps);
176+
@memset(maps, .empty);
177+
178+
try genMaps(seq, 1, maps);
179+
try printMap(1, maps);
180+
try genMaps(seq, 2, maps);
181+
try printMap(2, maps);
182+
183+
const occs = [_][]const u8{
184+
"GGT",
185+
"GGTA",
186+
"GGTATT",
187+
"GGTATTTTAATT",
188+
"GGTATTTTAATTTATAGT",
189+
};
190+
for (occs) |occ| {
191+
try genMaps(seq, occ.len, maps);
192+
try printOcc(occ, maps);
193+
}
194+
}

bench/bench_zig.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ problems:
5353
- name: knucleotide
5454
source:
5555
- 1.zig
56+
- 1-m.zig
5657
compiler_version_command: zig version
5758
compiler_version_regex:
5859
runtime_version_parameter:

0 commit comments

Comments
 (0)