stage2 提供近乎完整的功能但出现严重性能问题 添加stage0的原生性能记录 stage1添加更多内置函数 nob重构代码使其更加健壮
This commit is contained in:
1 parent
b5a7a811b3
commit
e02b0c633a
35 files changed
+14018
-216
No files matched your search
@@ -0,0 +1,303 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 全量热点/耗时记录器(配合 @file_name()/@line()/@col() 与 sys/time.spl)。
|
||||
// 每个调用点聚合:count、独占(self)与包含(total)的 steps/ns。
|
||||
// - total_* 含被调用者(嵌套会重复,跨站点求和会超过墙钟)
|
||||
// - self_* = total_* - 子区间之和,用于定位真正的热点
|
||||
// 用法(严格 LIFO,defer 正好满足):
|
||||
// var tok = Profile.begin(&prof, @file_name(), @line(), @col());
|
||||
// defer { Profile.end(&prof, tok); }
|
||||
|
||||
type .{ .Error } = @import("error.spl");
|
||||
type .{ .Vec } = @import("vec.spl");
|
||||
type .{ .ByteView } = @import("byte_view.spl");
|
||||
type .{ .ByteBuffer } = @import("byte_buffer.spl");
|
||||
type .{ .append_usize } = @import("fmt.spl");
|
||||
type .{ .open_cstr, .write_all } = @import("io.spl");
|
||||
type File = @import("../sys/file.spl");
|
||||
type Time = @import("../sys/time.spl");
|
||||
|
||||
type Site = struct {
|
||||
fname: ByteView,
|
||||
line: usize,
|
||||
col: usize,
|
||||
count: usize,
|
||||
self_steps: usize,
|
||||
total_steps: usize,
|
||||
self_ns: usize,
|
||||
total_ns: usize,
|
||||
}
|
||||
|
||||
type SpanRec = struct {
|
||||
site: usize,
|
||||
t0_ns: usize,
|
||||
t0_steps: usize,
|
||||
child_steps: usize,
|
||||
child_ns: usize,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
type Profile = @self;
|
||||
|
||||
sites: Vec, // Site
|
||||
spans: Vec, // SpanRec
|
||||
stack: Vec, // usize(活动 span 的 token,LIFO)
|
||||
has_time: bool,
|
||||
|
||||
fn new(): Profile {
|
||||
var has = false;
|
||||
if Time.available() != 0 { has = true; }
|
||||
ret Profile {
|
||||
.sites = Vec.blank(@sizeof(Site), @alignof(Site)),
|
||||
.spans = Vec.blank(@sizeof(SpanRec), @alignof(SpanRec)),
|
||||
.stack = Vec.blank(@sizeof(usize), @alignof(usize)),
|
||||
.has_time = has,
|
||||
};
|
||||
}
|
||||
|
||||
fn drop(self: *Profile) {
|
||||
Vec.drop(&self.stack);
|
||||
Vec.drop(&self.spans);
|
||||
Vec.drop(&self.sites);
|
||||
}
|
||||
|
||||
fn site_at(self: *Profile, i: usize): *Site {
|
||||
match Vec.get(&self.sites, i) {
|
||||
.Ok(ptr) => { ret ptr as *Site; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn span_at(self: *Profile, i: usize): *SpanRec {
|
||||
match Vec.get(&self.spans, i) {
|
||||
.Ok(ptr) => { ret ptr as *SpanRec; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn usize_at(v: *Vec, i: usize): usize {
|
||||
match Vec.get(v, i) {
|
||||
.Ok(ptr) => { ret (ptr as *usize).*; },
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
|
||||
fn find_site(self: *Profile, fname: []u8, line: usize, col: usize): usize {
|
||||
var fv = ByteView.from_slice(fname);
|
||||
var i: usize = 0;
|
||||
while i < self.sites.len {
|
||||
var s = site_at(self, i);
|
||||
if s != null && s.line == line && s.col == col && ByteView.eq(s.fname, fv) {
|
||||
ret i;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
var fresh = Site {
|
||||
.fname = fv,
|
||||
.line = line,
|
||||
.col = col,
|
||||
.count = 0,
|
||||
.self_steps = 0,
|
||||
.total_steps = 0,
|
||||
.self_ns = 0,
|
||||
.total_ns = 0,
|
||||
};
|
||||
match Vec.push(&self.sites, &fresh as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
ret self.sites.len - 1;
|
||||
}
|
||||
|
||||
// 仅计数(轻量点)。
|
||||
fn hit(self: *Profile, fname: []u8, line: usize, col: usize) {
|
||||
var site = find_site(self, fname, line, col);
|
||||
var s = site_at(self, site);
|
||||
if s != null { s.count = s.count + 1; }
|
||||
}
|
||||
|
||||
fn begin(self: *Profile, fname: []u8, line: usize, col: usize): usize {
|
||||
var site = find_site(self, fname, line, col);
|
||||
var s = site_at(self, site);
|
||||
if s != null { s.count = s.count + 1; }
|
||||
|
||||
var t_ns: usize = 0;
|
||||
if self.has_time { t_ns = Time.now_ns(); }
|
||||
var rec = SpanRec {
|
||||
.site = site,
|
||||
.t0_ns = t_ns,
|
||||
.t0_steps = Time.steps(),
|
||||
.child_steps = 0,
|
||||
.child_ns = 0,
|
||||
.active = true,
|
||||
};
|
||||
match Vec.push(&self.spans, &rec as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret 0; },
|
||||
}
|
||||
var token = self.spans.len - 1;
|
||||
match Vec.push(&self.stack, &token as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
ret token;
|
||||
}
|
||||
|
||||
fn end(self: *Profile, token: usize) {
|
||||
var rec = span_at(self, token);
|
||||
if rec == null || !rec.active { ret; }
|
||||
|
||||
var e_steps = Time.steps() - rec.t0_steps;
|
||||
var e_ns: usize = 0;
|
||||
if self.has_time { e_ns = Time.now_ns() - rec.t0_ns; }
|
||||
|
||||
var s = site_at(self, rec.site);
|
||||
if s != null {
|
||||
s.total_steps = s.total_steps + e_steps;
|
||||
s.self_steps = s.self_steps + (e_steps - rec.child_steps);
|
||||
if self.has_time {
|
||||
s.total_ns = s.total_ns + e_ns;
|
||||
s.self_ns = s.self_ns + (e_ns - rec.child_ns);
|
||||
}
|
||||
}
|
||||
rec.active = false;
|
||||
|
||||
// 弹栈并把区间计入父 span(LIFO)。
|
||||
var popped: usize = 0;
|
||||
match Vec.pop(&self.stack, &popped as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
var n = self.stack.len;
|
||||
if n > 0 {
|
||||
var p = span_at(self, usize_at(&self.stack, n - 1));
|
||||
if p != null {
|
||||
p.child_steps = p.child_steps + e_steps;
|
||||
p.child_ns = p.child_ns + e_ns;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- dump / save ----
|
||||
|
||||
fn out_s(out: *ByteBuffer, s: []u8): bool {
|
||||
match ByteBuffer.append(out, s) { .Ok => { ret true; }, .Err(error) => { ret false; } }
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn out_u(out: *ByteBuffer, v: usize): bool {
|
||||
match append_usize(out, v, 10, false) { .Ok => { ret true; }, .Err(error) => { ret false; } }
|
||||
ret false;
|
||||
}
|
||||
|
||||
// 列:count self_steps total_steps self_ns total_ns avg_self location;按 self_steps 降序。
|
||||
fn dump(self: *Profile, out: *ByteBuffer): bool {
|
||||
if !out_s(out, "profile sites=") { ret false; }
|
||||
if !out_u(out, self.sites.len) { ret false; }
|
||||
if !out_s(out, " has_time=") { ret false; }
|
||||
if !out_u(out, if self.has_time { 1 } else { 0 }) { ret false; }
|
||||
if !out_s(out, "\n") { ret false; }
|
||||
if !out_s(out, "count\tself_steps\ttotal_steps\tself_ns\ttotal_ns\tavg_self\tlocation\n") {
|
||||
ret false;
|
||||
}
|
||||
|
||||
var idx = Vec.blank(@sizeof(usize), @alignof(usize));
|
||||
defer { Vec.drop(&idx); }
|
||||
var i: usize = 0;
|
||||
while i < self.sites.len {
|
||||
match Vec.push(&idx, &i as *u8) { .Ok => {}, .Err(error) => {} }
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
i = 1;
|
||||
while i < idx.len {
|
||||
var j = i;
|
||||
while j > 0 {
|
||||
var a = site_at(self, usize_at(&idx, j - 1));
|
||||
var b = site_at(self, usize_at(&idx, j));
|
||||
var swap = false;
|
||||
if a != null && b != null {
|
||||
if b.self_steps > a.self_steps { swap = true; }
|
||||
}
|
||||
if !swap { break; }
|
||||
var tmp = usize_at(&idx, j - 1);
|
||||
match Vec.get(&idx, j - 1) { .Ok(p) => { (p as *usize).* = usize_at(&idx, j); }, .Err(e) => {} }
|
||||
match Vec.get(&idx, j) { .Ok(p) => { (p as *usize).* = tmp; }, .Err(e) => {} }
|
||||
j = j - 1;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while i < idx.len {
|
||||
var s = site_at(self, usize_at(&idx, i));
|
||||
if s != null {
|
||||
if !out_u(out, s.count) { ret false; }
|
||||
if !out_s(out, "\t") { ret false; }
|
||||
if !out_u(out, s.self_steps) { ret false; }
|
||||
if !out_s(out, "\t") { ret false; }
|
||||
if !out_u(out, s.total_steps) { ret false; }
|
||||
if !out_s(out, "\t") { ret false; }
|
||||
if !out_u(out, s.self_ns) { ret false; }
|
||||
if !out_s(out, "\t") { ret false; }
|
||||
if !out_u(out, s.total_ns) { ret false; }
|
||||
if !out_s(out, "\t") { ret false; }
|
||||
if s.count != 0 { if !out_u(out, s.self_steps / s.count) { ret false; } }
|
||||
else { if !out_s(out, "0") { ret false; } }
|
||||
if !out_s(out, "\t") { ret false; }
|
||||
if !out_s(out, ByteView.as_slice(s.fname)) { ret false; }
|
||||
if !out_s(out, ":") { ret false; }
|
||||
if !out_u(out, s.line) { ret false; }
|
||||
if !out_s(out, ":") { ret false; }
|
||||
if !out_u(out, s.col) { ret false; }
|
||||
if !out_s(out, "\n") { ret false; }
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
|
||||
fn save(self: *Profile, path: ByteView): bool {
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
if !dump(self, &out) { ret false; }
|
||||
|
||||
var f = open_cstr(path, ByteView.from_slice("wb"));
|
||||
if !File.valid(&f) { ret false; }
|
||||
var w = write_all(&f, ByteBuffer.as_slice(out));
|
||||
var closed = File.close(&f);
|
||||
match w {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
if closed != 0 { ret false; }
|
||||
ret true;
|
||||
}
|
||||
|
||||
// ---- 测试 ----
|
||||
|
||||
fn spin(n: usize): usize {
|
||||
var i: usize = 0;
|
||||
var s: usize = 0;
|
||||
while i < n { s = s + i; i = i + 1; }
|
||||
ret s;
|
||||
}
|
||||
|
||||
fn test_profile() {
|
||||
var p = new();
|
||||
defer { drop(&p); }
|
||||
|
||||
var tok = begin(&p, @file_name(), @line(), @col());
|
||||
var v = spin(1000);
|
||||
end(&p, tok);
|
||||
|
||||
@assert(p.sites.len >= 1, "sites");
|
||||
var s = site_at(&p, 0);
|
||||
@assert(s != null && s.count >= 1, "count");
|
||||
@assert(s.total_steps > 0, "steps");
|
||||
@assert(s.self_steps > 0, "self steps");
|
||||
if v == 0 { ignore_usize(v + 1); }
|
||||
}
|
||||
|
||||
fn ignore_usize(v: usize) {}
|
||||
|
||||
fn main() {
|
||||
test_profile();
|
||||
}
|
||||
Reference in new issue
Block a user