stage2 提供近乎完整的功能但出现严重性能问题 添加stage0的原生性能记录 stage1添加更多内置函数 nob重构代码使其更加健壮
This commit is contained in:
1 parent
b5a7a811b3
commit
e02b0c633a
35 files changed
+14018
-216
No files matched your search
@@ -24,6 +24,12 @@ fn new(): ByteBuffer {
|
||||
ret ByteBuffer { .storage = Vec.blank(1, 1) };
|
||||
}
|
||||
|
||||
fn with_capacity(cap: usize): ByteBuffer {
|
||||
var b = new();
|
||||
match reserve(&b, cap) { .Ok => {}, .Err(error) => {} }
|
||||
ret b;
|
||||
}
|
||||
|
||||
fn size(self: ByteBuffer): usize {
|
||||
ret self.storage.len;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
|
||||
type .{ .Error, .Status, .make } = @import("error.spl");
|
||||
|
||||
#extern(vm) fn vm_memcpy(dst: *any, src: *any, n: usize): *any;
|
||||
|
||||
fn ignore_ptr(p: *any) {}
|
||||
|
||||
type SizeResult = enum {
|
||||
Ok: usize,
|
||||
Err: Error,
|
||||
@@ -69,6 +73,18 @@ fn move(dst: []u8, src: []u8): Status {
|
||||
}
|
||||
if dst_addr == src_addr { ret .Ok; }
|
||||
|
||||
// 非重叠且较大时走原生 memcpy(Vec 扩容复制是常见场景)。
|
||||
var non_overlap = false;
|
||||
if dst_addr < src_addr {
|
||||
if src_addr - dst_addr >= count { non_overlap = true; }
|
||||
} else {
|
||||
if dst_addr - src_addr >= count { non_overlap = true; }
|
||||
}
|
||||
if non_overlap && count >= 16 {
|
||||
ignore_ptr(vm_memcpy(dst.ptr as *any, src.ptr as *any, count));
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
if dst_addr < src_addr {
|
||||
var i: usize = 0;
|
||||
while i < count {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
+39
-12
@@ -49,14 +49,17 @@ fn new(elem_size: usize, elem_align: usize): VecResult {
|
||||
ret .Ok(blank(elem_size, elem_align));
|
||||
}
|
||||
|
||||
fn valid(self: Vec): bool {
|
||||
if self.elem_size == 0 || !valid_alignment(self.elem_align) { ret false; }
|
||||
// 按指针接收,避免每次调用复制整个 Vec;内联对齐检查,省一次函数调用。
|
||||
fn valid(self: *Vec): bool {
|
||||
if self.elem_size == 0 { ret false; }
|
||||
var a = self.elem_align;
|
||||
if a == 0 || (a & (a - 1)) != 0 { ret false; }
|
||||
if self.len > self.cap { ret false; }
|
||||
if self.cap == 0 {
|
||||
ret self.raw == null && self.data == null && self.len == 0;
|
||||
}
|
||||
if self.raw == null || self.data == null { ret false; }
|
||||
ret (self.data as usize & (self.elem_align - 1)) == 0;
|
||||
ret (self.data as usize & (a - 1)) == 0;
|
||||
}
|
||||
|
||||
fn status_error(kind: Kind): Status {
|
||||
@@ -108,9 +111,8 @@ fn allocate(capacity: usize, elem_size: usize, elem_align: usize): VecResult {
|
||||
});
|
||||
}
|
||||
|
||||
// 保证至少可容纳 need 个元素。所有检查在原对象改变前完成。
|
||||
fn reserve(self: *Vec, need: usize): Status {
|
||||
if !valid(self.*) { ret status_error(.InvalidArgument); }
|
||||
// 与 reserve 相同,但假定调用方已校验 self(内部热路径用,省去重复 valid)。
|
||||
fn reserve_raw(self: *Vec, need: usize): Status {
|
||||
if need <= self.cap { ret .Ok; }
|
||||
|
||||
var new_cap = grow_capacity(self.cap, 8, need);
|
||||
@@ -140,14 +142,25 @@ fn reserve(self: *Vec, need: usize): Status {
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 保证至少可容纳 need 个元素(外部入口,含校验)。
|
||||
fn reserve(self: *Vec, need: usize): Status {
|
||||
if !valid(self) { ret status_error(.InvalidArgument); }
|
||||
ret reserve_raw(self, need);
|
||||
}
|
||||
|
||||
// 预分配恰好 capacity 个元素(等价“with_capacity”)。
|
||||
fn with_capacity(elem_size: usize, elem_align: usize, capacity: usize): VecResult {
|
||||
ret allocate(capacity, elem_size, elem_align);
|
||||
}
|
||||
|
||||
fn get(self: *Vec, index: usize): PtrResult {
|
||||
if !valid(self.*) { ret .Err(Error.make(.InvalidArgument)); }
|
||||
if !valid(self) { ret .Err(Error.make(.InvalidArgument)); }
|
||||
if index >= self.len { ret .Err(Error.make(.OutOfRange)); }
|
||||
ret .Ok(self.data + index * self.elem_size);
|
||||
}
|
||||
|
||||
fn set(self: *Vec, index: usize, elem: *u8): Status {
|
||||
if !valid(self.*) || elem == null { ret status_error(.InvalidArgument); }
|
||||
if !valid(self) || elem == null { ret status_error(.InvalidArgument); }
|
||||
if index >= self.len { ret status_error(.OutOfRange); }
|
||||
var dst = self.data + index * self.elem_size;
|
||||
ret move(dst[0..self.elem_size], elem[0..self.elem_size]);
|
||||
@@ -155,7 +168,7 @@ fn set(self: *Vec, index: usize, elem: *u8): Status {
|
||||
|
||||
// 追加 count 个连续元素。elems 可以指向 self 当前已初始化的元素区。
|
||||
fn append(self: *Vec, elems: *u8, count: usize): Status {
|
||||
if !valid(self.*) { ret status_error(.InvalidArgument); }
|
||||
if !valid(self) { ret status_error(.InvalidArgument); }
|
||||
if count == 0 { ret .Ok; }
|
||||
if elems == null { ret status_error(.InvalidArgument); }
|
||||
|
||||
@@ -190,7 +203,7 @@ fn append(self: *Vec, elems: *u8, count: usize): Status {
|
||||
}
|
||||
}
|
||||
|
||||
match reserve(self, new_len) {
|
||||
match reserve_raw(self, new_len) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
@@ -209,9 +222,23 @@ fn push(self: *Vec, elem: *u8): Status {
|
||||
ret append(self, elem, 1);
|
||||
}
|
||||
|
||||
// 热路径:假定 self 有效且已有容量时直接写入;容量不足才回退到 append。
|
||||
fn push_raw(self: *Vec, elem: *u8): bool {
|
||||
if self.len < self.cap {
|
||||
copy(self.data + self.len * self.elem_size, elem, self.elem_size);
|
||||
self.len = self.len + 1;
|
||||
ret true;
|
||||
}
|
||||
match append(self, elem, 1) {
|
||||
.Ok => { ret true; },
|
||||
.Err(error) => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
// 将末元素复制到 out,然后缩短数组。out 可以与元素区重叠。
|
||||
fn pop(self: *Vec, out: *u8): Status {
|
||||
if !valid(self.*) || out == null { ret status_error(.InvalidArgument); }
|
||||
if !valid(self) || out == null { ret status_error(.InvalidArgument); }
|
||||
if self.len == 0 { ret status_error(.OutOfRange); }
|
||||
var source = self.data + (self.len - 1) * self.elem_size;
|
||||
match move(out[0..self.elem_size], source[0..self.elem_size]) {
|
||||
@@ -229,7 +256,7 @@ fn clear(self: *Vec) {
|
||||
// 成功时先释放 out 原有存储再提交副本;失败保持 out 不变。
|
||||
// out 与 src 指向同一个 Vec 也允许。
|
||||
fn clone(out: *Vec, src: Vec): Status {
|
||||
if !valid(out.*) || !valid(src) { ret status_error(.InvalidArgument); }
|
||||
if !valid(out) || !valid(&src) { ret status_error(.InvalidArgument); }
|
||||
if out.elem_size != src.elem_size || out.elem_align != src.elem_align {
|
||||
ret status_error(.InvalidArgument);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user