Skip to content

different strategy for tokenizing keywords - #5442

Merged
andrewrk merged 1 commit into
masterfrom
tokenizer-perf
May 26, 2020
Merged

different strategy for tokenizing keywords#5442
andrewrk merged 1 commit into
masterfrom
tokenizer-perf

Conversation

@andrewrk

Copy link
Copy Markdown
Member

I did my idea from #5359 (comment)

throughput: 279 MiB/s => 347 MiB/s

throughput: 279 MiB/s => 347 MiB/s
@andrewrk
andrewrk requested a review from VexuMay 26, 2020 18:10
@andrewrkandrewrk mentioned this pull request May 26, 2020

@VexuVexu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be faster but could you also run the throughput test for the hashmap version?

@andrewrk

Copy link
Copy Markdown
MemberAuthor

benchmark code:

conststd=@import("std");
pubfnsetup(gpa: *std.mem.Allocator, options: *Options) !void {}
varglobal_total_byte_count: u64=undefined;
varglobal_total_token_count: u64=undefined;
pubfnrun(gpa: *std.mem.Allocator, context: void) !void {
varwalker=trystd.fs.walkPath(gpa, "../lib/std");
deferwalker.deinit();
vartotal_byte_count: u64=0;
vartotal_token_count: u64=0;
while (trywalker.next()) |entry|switch (entry.kind) {
.File=> {
if (!std.mem.endsWith(u8, entry.basename, ".zig")) continue;
constsource=tryentry.dir.readFileAlloc(gpa, entry.basename, 30*1024*1024);
defergpa.free(source);
total_byte_count+=source.len;
vartokenizer=std.zig.Tokenizer.init(source);
while (true) {
consttoken=tokenizer.next();
total_token_count+=1;
if (token.id==.Eof) break;
}
},
else=>continue,
};
global_total_byte_count=total_byte_count;
global_total_token_count=total_token_count;
}
pubconstMeasurement=struct {
median: u64,
mean: u64,
min: u64,
max: u64,
fncompute(all_samples: []Sample, comptimefield: []constu8) Measurement {
constS=struct {
fnorder(a: Sample, b: Sample) bool {
return@field(a, field) <@field(b, field);
}
};
// Remove the 2 outliersstd.sort.sort(Sample, all_samples, S.order);
constsamples=all_samples[1..all_samples.len-1];
// Compute statsvartotal: u64=0;
varmin: u64=std.math.maxInt(u64);
varmax: u64=0;
for (samples) |s| {
constv=@field(s, field);
total+=v;
if (v<min) min=v;
if (v>max) max=v;
}
return .{
.median=@field(samples[samples.len/2], field),
.mean=total/samples.len,
.min=min,
.max=max,
};
}
};
pubconstResults=union(enum) {
fail: anyerror,
ok: struct {
samples_taken: usize,
wall_time: Measurement,
utime: Measurement,
stime: Measurement,
maxrss: usize,
},
};
constSample=struct {
wall_time: u64,
utime: u64,
stime: u64,
};
fntimeval_to_ns(timeval: std.os.timeval) u64 {
constns_per_us=std.time.ns_per_s/std.time.us_per_s;
return@bitCast(usize, timeval.tv_sec) *std.time.ns_per_s+@bitCast(usize, timeval.tv_usec) *ns_per_us;
}
varsamples_buf: [1000000]Sample=undefined;
constmax_nano_seconds=std.time.ns_per_s*20;
pubfnbench(options: Options, comptimefunc: var, args: var) Results {
varsample_index: usize=0;
consttimer=std.time.Timer.start() catch@panic("need timer to work");
constfirst_start=timer.read();
while ((sample_index<3or
(timer.read() -first_start) <max_nano_seconds) andsample_index<samples_buf.len)
{
conststart_rusage=std.os.getrusage(options.rusage_who);
conststart=timer.read();
@call(.{}, func, args) catch|err| {
return .{ .fail=err };
};
constend=timer.read();
constend_rusage=std.os.getrusage(options.rusage_who);
samples_buf[sample_index] = .{
.wall_time=end-start,
.utime=timeval_to_ns(end_rusage.utime) -timeval_to_ns(start_rusage.utime),
.stime=timeval_to_ns(end_rusage.stime) -timeval_to_ns(start_rusage.stime),
};
sample_index+=1;
}
constall_samples=samples_buf[0..sample_index];
constwall_time=Measurement.compute(all_samples, "wall_time");
constutime=Measurement.compute(all_samples, "utime");
conststime=Measurement.compute(all_samples, "stime");
constfinal_rusage=std.os.getrusage(options.rusage_who);
std.debug.warn("total_token_count={}\ntotal_byte_count={}\n", .{global_total_token_count, global_total_byte_count});
{
constelapsed_s=@intToFloat(f64, wall_time.mean) /std.time.ns_per_s;
constthroughput=@floatToInt(u64, @intToFloat(f64, global_total_byte_count) /elapsed_s);
std.debug.warn("throughput (mean): {Bi}/s\n", .{throughput});
}
{
constelapsed_s=@intToFloat(f64, wall_time.median) /std.time.ns_per_s;
constthroughput=@floatToInt(u64, @intToFloat(f64, global_total_byte_count) /elapsed_s);
std.debug.warn("throughput (median): {Bi}/s\n", .{throughput});
}
{
constelapsed_s=@intToFloat(f64, wall_time.max) /std.time.ns_per_s;
constthroughput=@floatToInt(u64, @intToFloat(f64, global_total_byte_count) /elapsed_s);
std.debug.warn("throughput (min): {Bi}/s\n", .{throughput});
}
{
constelapsed_s=@intToFloat(f64, wall_time.min) /std.time.ns_per_s;
constthroughput=@floatToInt(u64, @intToFloat(f64, global_total_byte_count) /elapsed_s);
std.debug.warn("throughput (max): {Bi}/s\n", .{throughput});
}
return .{
.ok= .{
.samples_taken=all_samples.len,
.wall_time=wall_time,
.utime=utime,
.stime=stime,
.maxrss=@bitCast(usize, final_rusage.maxrss),
},
};
}
pubconstOptions=struct {
rusage_who: i32=std.os.RUSAGE_SELF,
};
pubfnmain() !void {
constgpa=if (std.builtin.link_libc) std.heap.c_allocatorelsestd.heap.page_allocator;
varoptions: Options= .{};
constcontext=trysetup(gpa, &options);
constresults=bench(options, run, .{ gpa, context });
trystd.json.stringify(results, std.json.StringifyOptions{}, std.io.getStdOut().outStream());
}

run instructions:

./zig run benchmark.zig --release-fast -lc

master:

throughput (mean): 285.748743057251MiB/s
throughput (median): 285.61156463623047MiB/s
throughput (min): 276.09959506988525MiB/s
throughput (max): 292.609414100647MiB/s
{"samples_taken":342,"wall_time":{"median":58652599,"mean":58624442,"min":57249903,"max":60673253},"utime":{"median":56092000,"mean":55936076,"min":51945000,"max":58686000},"stime":{"median":2979000,"mean":2686317,"min":978000,"max":6031000},"maxrss":280412}

comptime hashmap:

throughput (mean): 334.53428840637207MiB/s
throughput (median): 333.30327892303467MiB/s
throughput (min): 326.11223888397217MiB/s
throughput (max): 341.7137613296509MiB/s
{"samples_taken":400,"wall_time":{"median":50271341,"mean":50086354,"min":49034030,"max":51379865},"utime":{"median":47398000,"mean":47257206,"min":43324000,"max":49551000},"stime":{"median":2985000,"mean":2827223,"min":971000,"max":6981000},"maxrss":299060}

this branch:

throughput (mean): 345.59295654296875MiB/s
throughput (median): 345.70947074890137MiB/s
throughput (min): 333.6345806121826MiB/s
throughput (max): 363.7453269958496MiB/s
{"samples_taken":413,"wall_time":{"median":48459932,"mean":48476270,"min":46057107,"max":50213792},"utime":{"median":45891000,"mean":45711233,"min":41717000,"max":48545000},"stime":{"median":2963000,"mean":2756588,"min":957000,"max":7042000},"maxrss":33812}

@andrewrk

Copy link
Copy Markdown
MemberAuthor

I think it might be useful to start collecting instruction counts rather than wall clock timings because the wall clock timings tend to be highly variable for me

@andrewrk

Copy link
Copy Markdown
MemberAuthor

benchmark code:

conststd=@import("std");
pubfnsetup(gpa: *std.mem.Allocator, options: *Options) !void {}
varglobal_total_byte_count: u64=undefined;
varglobal_total_token_count: u64=undefined;
pubfnrun(gpa: *std.mem.Allocator, context: void) !void {
varwalker=trystd.fs.walkPath(gpa, "../lib/std");
deferwalker.deinit();
vartotal_byte_count: u64=0;
vartotal_token_count: u64=0;
while (trywalker.next()) |entry|switch (entry.kind) {
.File=> {
if (!std.mem.endsWith(u8, entry.basename, ".zig")) continue;
constsource=tryentry.dir.readFileAlloc(gpa, entry.basename, 30*1024*1024);
defergpa.free(source);
total_byte_count+=source.len;
vartokenizer=std.zig.Tokenizer.init(source);
while (true) {
consttoken=tokenizer.next();
total_token_count+=1;
if (token.id==.Eof) break;
}
},
else=>continue,
};
global_total_byte_count=total_byte_count;
global_total_token_count=total_token_count;
}
pubconstOptions=struct {
rusage_who: i32=std.os.RUSAGE_SELF,
};
pubfnmain() !void {
constgpa=if (std.builtin.link_libc) std.heap.c_allocatorelsestd.heap.page_allocator;
varoptions: Options= .{};
constcontext=trysetup(gpa, &options);
tryrun(gpa, context);
}

run instructions:

./zig build-exe bench.zig --release-fast -lc
perf stat -e cache-misses -e instructions -e cycles ./bench

master:

 64,292 cache-misses:u 689,888,091 instructions:u # 3.21 insn per cycle 214,993,208 cycles:u 

comptime hash map:

 93,768 cache-misses:u 562,638,208 instructions:u # 3.00 insn per cycle 187,493,863 cycles:u 

this branch:

 62,699 cache-misses:u 550,980,802 instructions:u # 2.96 insn per cycle 186,078,285 cycles:u 

@data-man

Copy link
Copy Markdown
Contributor

I think it might be useful to start collecting instruction counts rather than wall clock timings because the wall clock timings tend to be highly variable for me

BTW, I thought about the built-in benchmarking, and then I found #1010.
I'd be happy to start working on it. :)

@squeek502

Copy link
Copy Markdown
Member

An attempt at making this lookup implementation reusable: https://gist.github.com/squeek502/7f7db10c520bacff4c24b22c9102db8d

@andrewrk

Copy link
Copy Markdown
MemberAuthor

@squeek502 oh of course, this could be a generic improvement to std.meta.stringToEnum. Your code looks good. Also the weird struct @"0" thing would be solved by #4335 I believe.

Want to make that a follow-up PR?

@andrewrk
andrewrk merged commit ba41a9d into masterMay 26, 2020
@andrewrk
andrewrk deleted the tokenizer-perf branch May 26, 2020 22:59
@squeek502

squeek502 commented May 26, 2020

Copy link
Copy Markdown
Member

Sure, but note that this implementation of stringToEnum will fail for large Enums due to #4055. Could choose the implementation based on the size of the Enum, though.

Where do you think this stringLookup function should go in the std library (and what should it be called)?

EDIT: PR created, see #5452

andrewrk added a commit that referenced this pull request May 29, 2020
Add std.ComptimeStringMap based on the tokenizer optimization in #5442
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@andrewrk@data-man@squeek502@Vexu