From ec1d9df37dba4a17065b091bbe9e03c9635cd0dc Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Mon, 15 Apr 2024 12:36:36 +0200 Subject: prog: profile what consumes space in exec encoding Allow to profile how many bytes are consumed for what in the exec encoding. The profile shows there are not many opportunities left. 53% are consumed by data blobs. 13% for const args. 18% for non-arg things (syscall number, copyout index, props, etc). --- prog/decodeexec.go | 69 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 27 deletions(-) (limited to 'prog/decodeexec.go') diff --git a/prog/decodeexec.go b/prog/decodeexec.go index 2113d24c7..2356e051b 100644 --- a/prog/decodeexec.go +++ b/prog/decodeexec.go @@ -7,6 +7,7 @@ import ( "encoding/binary" "fmt" "reflect" + "strings" ) type ExecProg struct { @@ -71,8 +72,8 @@ type ExecCsumChunk struct { Size uint64 } -func (target *Target) DeserializeExec(exec []byte) (ExecProg, error) { - dec := &execDecoder{target: target, data: exec} +func (target *Target) DeserializeExec(exec []byte, stats map[string]int) (ExecProg, error) { + dec := &execDecoder{target: target, data: exec, stats: stats} dec.parse() if dec.err != nil { return ExecProg{}, dec.err @@ -96,22 +97,23 @@ type execDecoder struct { vars []uint64 call ExecCall calls []ExecCall + stats map[string]int } func (dec *execDecoder) parse() { for dec.err == nil { - switch instr := dec.read(); instr { + switch instr := dec.read("instr/opcode"); instr { case execInstrCopyin: dec.commitCall() dec.call.Copyin = append(dec.call.Copyin, ExecCopyin{ - Addr: dec.read() + dec.target.DataOffset, + Addr: dec.read("instr/copyin") + dec.target.DataOffset, Arg: dec.readArg(), }) case execInstrCopyout: dec.call.Copyout = append(dec.call.Copyout, ExecCopyout{ - Index: dec.read(), - Addr: dec.read() + dec.target.DataOffset, - Size: dec.read(), + Index: dec.read("instr/copyout/index"), + Addr: dec.read("instr/copyout/addr") + dec.target.DataOffset, + Size: dec.read("instr/copyout/size"), }) case execInstrEOF: dec.commitCall() @@ -126,8 +128,8 @@ func (dec *execDecoder) parse() { return } dec.call.Meta = dec.target.Syscalls[instr] - dec.call.Index = dec.read() - for i := dec.read(); i > 0; i-- { + dec.call.Index = dec.read("instr/index") + for i := dec.read("instr/nargs"); i > 0; i-- { switch arg := dec.readArg(); arg.(type) { case ExecArgConst, ExecArgResult: dec.call.Args = append(dec.call.Args, arg) @@ -142,7 +144,7 @@ func (dec *execDecoder) parse() { func (dec *execDecoder) readCallProps(props *CallProps) { props.ForeachProp(func(_, _ string, value reflect.Value) { - arg := dec.read() + arg := dec.read("call prop") switch kind := value.Kind(); kind { case reflect.Int: value.SetInt(int64(arg)) @@ -157,11 +159,11 @@ func (dec *execDecoder) readCallProps(props *CallProps) { } func (dec *execDecoder) readArg() ExecArg { - switch typ := dec.read(); typ { + switch typ := dec.read("arg/type"); typ { case execArgConst: - meta := dec.read() + meta := dec.read("arg/const/meta") return ExecArgConst{ - Value: dec.read(), + Value: dec.read("arg/const/value"), Size: meta & 0xff, Format: BinaryFormat((meta >> 8) & 0xff), BitfieldOffset: (meta >> 16) & 0xff, @@ -176,18 +178,18 @@ func (dec *execDecoder) readArg() ExecArg { size = 8 } return ExecArgConst{ - Value: dec.read() + dec.target.DataOffset, + Value: dec.read("arg/addr") + dec.target.DataOffset, Size: uint64(size), } case execArgResult: - meta := dec.read() + meta := dec.read("arg/result/meta") arg := ExecArgResult{ Size: meta & 0xff, Format: BinaryFormat((meta >> 8) & 0xff), - Index: dec.read(), - DivOp: dec.read(), - AddOp: dec.read(), - Default: dec.read(), + Index: dec.read("arg/result/index"), + DivOp: dec.read("arg/result/divop"), + AddOp: dec.read("arg/result/addop"), + Default: dec.read("arg/result/default"), } for uint64(len(dec.vars)) <= arg.Index { dec.vars = append(dec.vars, 0) @@ -195,22 +197,23 @@ func (dec *execDecoder) readArg() ExecArg { dec.vars[arg.Index] = arg.Default return arg case execArgData: - flags := dec.read() + flags := dec.read("arg/data/size") size := flags & ^execArgDataReadable + dec.addStat("arg/data/blob", int(size)) readable := flags&execArgDataReadable != 0 return ExecArgData{ Data: dec.readBlob(size), Readable: readable, } case execArgCsum: - size := dec.read() - switch kind := dec.read(); kind { + size := dec.read("arg/csum/size") + switch kind := dec.read("arg/csum/kind"); kind { case ExecArgCsumInet: - chunks := make([]ExecCsumChunk, dec.read()) + chunks := make([]ExecCsumChunk, dec.read("arg/csum/chunks")) for i := range chunks { - kind := dec.read() - addr := dec.read() - size := dec.read() + kind := dec.read("arg/csum/chunk/kind") + addr := dec.read("arg/csum/chunk/addr") + size := dec.read("arg/csum/chunk/size") if kind == ExecArgCsumChunkData { addr += dec.target.DataOffset } @@ -235,7 +238,7 @@ func (dec *execDecoder) readArg() ExecArg { } } -func (dec *execDecoder) read() uint64 { +func (dec *execDecoder) read(stat string) uint64 { if dec.err != nil { return 0 } @@ -244,6 +247,7 @@ func (dec *execDecoder) read() uint64 { dec.setErr(fmt.Errorf("exec program overflow")) return 0 } + dec.addStat(stat, n) dec.data = dec.data[n:] return uint64(v) } @@ -281,3 +285,14 @@ func (dec *execDecoder) commitCall() { dec.calls = append(dec.calls, dec.call) dec.call = ExecCall{} } + +func (dec *execDecoder) addStat(stat string, n int) { + if dec.stats == nil { + return + } + prefix := "" + for _, part := range strings.Split(stat, "/") { + dec.stats[prefix+part] += n + prefix += part + "/" + } +} -- cgit mrf-deployment