From 0ea751fe1a152621e57c8997bbbdf62efa9ec236 Mon Sep 17 00:00:00 2001 From: deelawn Date: Fri, 10 May 2024 15:35:20 -0700 Subject: [PATCH] wip move from other branch --- benchmarking/cmd/main.go | 107 +++++++++++++++++++++++++++++++ benchmarking/exporter.go | 46 +++++++++++++ benchmarking/init.go | 6 ++ benchmarking/measurement.go | 37 +++++++++++ benchmarking/ops.go | 62 ++++++++++++++++++ benchmarking/ops_disabled.go | 5 ++ benchmarking/ops_enabled.go | 5 ++ benchmarking/stack.go | 66 +++++++++++++++++++ benchmarking/storage_disabled.go | 5 ++ benchmarking/storage_enabled.go | 5 ++ benchmarking/timer.go | 22 +++++++ gno.land/cmd/gnoland/start.go | 5 ++ gnovm/pkg/gnolang/machine.go | 13 ++++ gnovm/pkg/gnolang/store.go | 20 ++++++ 14 files changed, 404 insertions(+) create mode 100644 benchmarking/cmd/main.go create mode 100644 benchmarking/exporter.go create mode 100644 benchmarking/init.go create mode 100644 benchmarking/measurement.go create mode 100644 benchmarking/ops.go create mode 100644 benchmarking/ops_disabled.go create mode 100644 benchmarking/ops_enabled.go create mode 100644 benchmarking/stack.go create mode 100644 benchmarking/storage_disabled.go create mode 100644 benchmarking/storage_enabled.go create mode 100644 benchmarking/timer.go diff --git a/benchmarking/cmd/main.go b/benchmarking/cmd/main.go new file mode 100644 index 00000000000..14641e2e14d --- /dev/null +++ b/benchmarking/cmd/main.go @@ -0,0 +1,107 @@ +package main + +import ( + "encoding/binary" + "flag" + "fmt" + "os" + "sync" + + "github.com/gnolang/gno/benchmarking" + "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +const recordSize int = 10 + +var pathFlag = flag.String("path", "", "the path to the benchmark file") + +func main() { + flag.Parse() + + file, err := os.Open(*pathFlag) + if err != nil { + panic("could not create benchmark file: " + err.Error()) + } + defer file.Close() + + inputCh := make(chan []byte, 10000) + outputCh := make(chan string, 10000) + wg := sync.WaitGroup{} + numWorkers := 1 + wg.Add(numWorkers) + + doneCh := make(chan struct{}) + + for i := 0; i < numWorkers; i++ { + go func() { + for { + record, ok := <-inputCh + if !ok { + break + } + + opName := gnolang.Op(record[0]).String() + if record[1] != 0 { + opName = benchmarking.StoreCodeString(record[1]) + } + + elapsedTime := binary.LittleEndian.Uint32(record[2:]) + size := binary.LittleEndian.Uint32(record[6:]) + outputCh <- opName + "," + fmt.Sprint(elapsedTime) + "," + fmt.Sprint(size) + } + wg.Done() + }() + } + + go func() { + out, err := os.Create("results.csv") + if err != nil { + panic("could not create readable output file: " + err.Error()) + } + defer out.Close() + fmt.Fprintln(out, "op,elapsedTime,diskIOBytes") + + for { + output, ok := <-outputCh + if !ok { + break + } + + fmt.Fprintln(out, output) + } + + out.Close() + doneCh <- struct{}{} + }() + + var i int + + bufSize := recordSize * 100000 + buf := make([]byte, bufSize) + + for { + nbytes, err := file.Read(buf) + + if err != nil && nbytes == 0 { + break + } + n := nbytes / recordSize + + for j := 0; j < n; j++ { + inputCh <- buf[j*recordSize : (j+1)*recordSize] + } + + i += bufSize / recordSize + if i%1000 == 0 { + fmt.Println(i) + } + } + + close(inputCh) + wg.Wait() + close(outputCh) + <-doneCh + close(doneCh) + + fmt.Println("done") +} diff --git a/benchmarking/exporter.go b/benchmarking/exporter.go new file mode 100644 index 00000000000..2effa9bc7a8 --- /dev/null +++ b/benchmarking/exporter.go @@ -0,0 +1,46 @@ +package benchmarking + +import ( + "encoding/binary" + "fmt" + "os" + "time" +) + +var fileWriter *exporter + +func initExporter(fileName string) { + file, err := os.Create(fileName) + if err != nil { + panic("could not create benchmark file: " + err.Error()) + } + + fileWriter = &exporter{ + file: file, + } +} + +type exporter struct { + file *os.File +} + +func (e *exporter) export(code Code, elapsedTime time.Duration, size uint32) { + buf := []byte{code[0], code[1], 0, 0, 0, 0, 0, 0, 0, 0} + binary.LittleEndian.PutUint32(buf[2:], uint32(elapsedTime)) + binary.LittleEndian.PutUint32(buf[6:], size) + _, err := e.file.Write(buf) + if err != nil { + panic("could not write to benchmark file: " + err.Error()) + } +} + +func (e *exporter) close() { + e.file.Sync() + e.file.Close() +} + +func Finish() { + fmt.Println("## StackSize: ", stackSize) + + fileWriter.close() +} diff --git a/benchmarking/init.go b/benchmarking/init.go new file mode 100644 index 00000000000..2cdb586d77c --- /dev/null +++ b/benchmarking/init.go @@ -0,0 +1,6 @@ +package benchmarking + +func Init(filepath string) { + initExporter(filepath) + initStack() +} diff --git a/benchmarking/measurement.go b/benchmarking/measurement.go new file mode 100644 index 00000000000..28dc6749d7d --- /dev/null +++ b/benchmarking/measurement.go @@ -0,0 +1,37 @@ +package benchmarking + +import ( + "time" +) + +type measurement struct { + *timer + code Code + allocation uint32 +} + +func startNewMeasurement(code Code) *measurement { + return &measurement{ + timer: &timer{startTime: time.Now()}, + code: code, + } +} + +func (m *measurement) pause() { + m.stop() +} + +func (m *measurement) resume() { + m.start() +} + +func (m *measurement) end(size uint32) { + m.stop() + if size != 0 && m.allocation != 0 { + panic("measurement cannot have both allocation and size") + } else if size == 0 { + size = m.allocation + } + + fileWriter.export(m.code, m.elapsedTime, size) +} diff --git a/benchmarking/ops.go b/benchmarking/ops.go new file mode 100644 index 00000000000..92d1abd81c9 --- /dev/null +++ b/benchmarking/ops.go @@ -0,0 +1,62 @@ +package benchmarking + +// store code +const ( + StoreGetObject byte = 0x01 // get value and unmarshl to object from store + StoreSetObject byte = 0x02 // marshal object and set value in store + StoreDeleteObject byte = 0x03 // delete value from store + StoreGetPackage byte = 0x04 // get package from store + StoreSetPackage byte = 0x05 // get package from store + StoreGetType byte = 0x06 // get type from store + StoreSetType byte = 0x07 // set type in store + StoreGetBlockNode byte = 0x08 // get block node from store + StoreSetBlockNode byte = 0x09 // set block node in store + StoreAddMemPackage byte = 0x0A // add mempackage to store + StoreGetMemPackage byte = 0x0B // get mempackage from store + FinalizeTx byte = 0x0C // finalize realm transaction + + AminoMarshal byte = 0x0D // marshal go object to binary value + AminoUnMarshal byte = 0x0E // unmarshl binary value to go object + + StoreGet byte = 0x0F // Get binary value by key + StoreSet byte = 0x10 // Set binary value by key + + invalidStoreCode string = "StoreInvalid" +) + +var storeCodeNames = []string{ + invalidStoreCode, + "StoreGetObject", + "StoreSetObject", + "StoreDeleteObject", + "StoreGetPackage", + "StoreSetPackage", + "StoreGetType", + "StoreSetType", + "StoreGetBlockNode", + "StoreSetBlockNode", + "StoreAddMemPackage", + "StoreGetMemPackage", + "FinalizeTx", + "AminoMarshal", + "AminoUnMarshal", + "StoreGet", + "StoreSet", +} + +type Code [2]byte + +func VMOpCode(opCode byte) Code { + return [2]byte{opCode, 0x00} +} + +func StoreCode(storeCode byte) Code { + return [2]byte{0x00, storeCode} +} + +func StoreCodeString(storeCode byte) string { + if int(storeCode) >= len(storeCodeNames) { + return invalidStoreCode + } + return storeCodeNames[storeCode] +} diff --git a/benchmarking/ops_disabled.go b/benchmarking/ops_disabled.go new file mode 100644 index 00000000000..84482067d83 --- /dev/null +++ b/benchmarking/ops_disabled.go @@ -0,0 +1,5 @@ +//go:build !benchmarkingops + +package benchmarking + +const OpsEnabled = false diff --git a/benchmarking/ops_enabled.go b/benchmarking/ops_enabled.go new file mode 100644 index 00000000000..836cfd57a23 --- /dev/null +++ b/benchmarking/ops_enabled.go @@ -0,0 +1,5 @@ +//go:build benchmarkingops + +package benchmarking + +const OpsEnabled = true diff --git a/benchmarking/stack.go b/benchmarking/stack.go new file mode 100644 index 00000000000..e16b0278e02 --- /dev/null +++ b/benchmarking/stack.go @@ -0,0 +1,66 @@ +package benchmarking + +const initStackSize int = 64 + +var ( + measurementStack []*measurement + stackSize int +) + +func initStack() { + measurementStack = make([]*measurement, initStackSize) +} + +func StartMeasurement(code Code) { + if stackSize != 0 { + measurementStack[stackSize-1].pause() + } + + if stackSize == len(measurementStack) { + newStack := make([]*measurement, stackSize*2) + copy(newStack, measurementStack) + measurementStack = newStack + } + + measurementStack[stackSize] = startNewMeasurement(code) + stackSize++ +} + +// Pause pauses current measurement on the stack +func Pause() { + if stackSize != 0 { + measurementStack[stackSize-1].pause() + } +} + +// Resume resumes current measurement on the stack +func Resume() { + if stackSize != 0 { + measurementStack[stackSize-1].resume() + } +} + +// StopMeasurement ends the current measurement and resumes the previous one +// if one exists. It accepts the number of bytes that were read/written to/from +// the store. This value is zero if the operation is not a read or write. +func StopMeasurement(size uint32) { + if stackSize == 0 { + return + } + + measurementStack[stackSize-1].end(size) + + stackSize-- + + if stackSize != 0 { + measurementStack[stackSize-1].resume() + } +} + +func RecordAllocation(size uint32) { + if stackSize == 0 { + return + } + + measurementStack[stackSize-1].allocation += size +} diff --git a/benchmarking/storage_disabled.go b/benchmarking/storage_disabled.go new file mode 100644 index 00000000000..a42029f47a9 --- /dev/null +++ b/benchmarking/storage_disabled.go @@ -0,0 +1,5 @@ +//go:build !benchmarkingstorage + +package benchmarking + +const StorageEnabled = false diff --git a/benchmarking/storage_enabled.go b/benchmarking/storage_enabled.go new file mode 100644 index 00000000000..2cb7cf976d2 --- /dev/null +++ b/benchmarking/storage_enabled.go @@ -0,0 +1,5 @@ +//go:build benchmarkingstorage + +package benchmarking + +const StorageEnabled = true diff --git a/benchmarking/timer.go b/benchmarking/timer.go new file mode 100644 index 00000000000..20afdb723a2 --- /dev/null +++ b/benchmarking/timer.go @@ -0,0 +1,22 @@ +package benchmarking + +import "time" + +type timer struct { + startTime time.Time + elapsedTime time.Duration + isStopped bool +} + +func (t *timer) start() { + t.startTime = time.Now() +} + +func (t *timer) stop() { + if t.isStopped { + return + } + + t.elapsedTime += time.Since(t.startTime) + t.isStopped = true +} diff --git a/gno.land/cmd/gnoland/start.go b/gno.land/cmd/gnoland/start.go index aec69d5b338..1b02a0239ea 100644 --- a/gno.land/cmd/gnoland/start.go +++ b/gno.land/cmd/gnoland/start.go @@ -9,6 +9,7 @@ import ( "strings" "time" + bm "github.com/gnolang/gno/benchmarking" "github.com/gnolang/gno/gno.land/pkg/gnoland" "github.com/gnolang/gno/gno.land/pkg/log" "github.com/gnolang/gno/gnovm/pkg/gnoenv" @@ -201,6 +202,10 @@ func (c *startCfg) RegisterFlags(fs *flag.FlagSet) { } func execStart(c *startCfg, io commands.IO) error { + if bm.OpsEnabled || bm.StorageEnabled { + bm.Init("benchmarks.bin") + } + // Get the absolute path to the node's data directory nodeDir, err := filepath.Abs(c.dataDir) if err != nil { diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 2e0fd909ed7..f9b5a44edac 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -12,6 +12,7 @@ import ( "sync" "testing" + bm "github.com/gnolang/gno/benchmarking" "github.com/gnolang/gno/tm2/pkg/errors" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/store" @@ -1119,11 +1120,19 @@ const ( func (m *Machine) Run() { for { op := m.PopOp() + + if bm.OpsEnabled { + bm.StartMeasurement(bm.VMOpCode(byte(op))) + } + // TODO: this can be optimized manually, even into tiers. switch op { /* Control operators */ case OpHalt: m.incrCPU(OpCPUHalt) + if bm.OpsEnabled { + bm.StopMeasurement(0) + } return case OpNoop: m.incrCPU(OpCPUNoop) @@ -1442,6 +1451,10 @@ func (m *Machine) Run() { default: panic(fmt.Sprintf("unexpected opcode %s", op.String())) } + + if bm.OpsEnabled { + bm.StopMeasurement(0) + } } } diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index d15976ec262..fc91255fab7 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -2,10 +2,12 @@ package gnolang import ( "fmt" + "log" "reflect" "strconv" "strings" + bm "github.com/gnolang/gno/benchmarking" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/store" @@ -212,6 +214,15 @@ func (ds *defaultStore) SetCachePackage(pv *PackageValue) { // Some atomic operation. func (ds *defaultStore) GetPackageRealm(pkgPath string) (rlm *Realm) { + var size uint32 + if bm.StorageEnabled { + bm.StartMeasurement(bm.StoreCode(bm.StoreGetPackage)) + defer func() { + bm.StopMeasurement(size) + log.Printf("benchmark.StoreGetPackage, %d\n", size) + }() + } + oid := ObjectIDFromPkgPath(pkgPath) key := backendRealmKey(oid) bz := ds.baseStore.Get([]byte(key)) @@ -230,6 +241,15 @@ func (ds *defaultStore) GetPackageRealm(pkgPath string) (rlm *Realm) { // An atomic operation to set the package realm info (id counter etc). func (ds *defaultStore) SetPackageRealm(rlm *Realm) { + var size uint32 + if bm.StorageEnabled { + bm.StartMeasurement(bm.StoreCode(bm.StoreSetPackage)) + defer func() { + bm.StopMeasurement(size) + log.Printf("benchmark.StoreSetPackage, %d\n", size) + }() + } + oid := ObjectIDFromPkgPath(rlm.Path) key := backendRealmKey(oid) bz := amino.MustMarshal(rlm)