From 91176e4b245075e1f0bb23b9aeb3f1132c5e29ae Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Sat, 28 Dec 2024 15:26:02 -0800 Subject: [PATCH] perf(gnovm): cache PkgIDFromPkgPath for higher performance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change comes from noticing that PkgIDFromPkgPath is very heavily invoked within the VM yet its results with the same inputs produced deterministic results aka SHA256(path)[:20] Previously just spinning up the VM would take 80 seconds, with this change that's shaved by ~8-10 seconds down and with repeatable and visible results exhibited through: - Benchmark: ```shell $ benchstat before.txt after.txt name old time/op new time/op delta PkgIDFromPkgPath-8 1.96µs ± 2% 0.35µs ± 1% -82.40% (p=0.000 n=20+18) name old alloc/op new alloc/op delta PkgIDFromPkgPath-8 296B ± 0% 168B ± 0% -43.24% (p=0.000 n=20+20) name old allocs/op new allocs/op delta PkgIDFromPkgPath-8 9.00 ± 0% 7.00 ± 0% -22.22% (p=0.000 n=20+20) ``` - Profiles: * Before ```shell (pprof) list PkgIDFromPkgPath Total: 100.94s ROUTINE ======================== github.com/gnolang/gno/gnovm/pkg/gnolang.PkgIDFromPkgPath in $PATH 220ms 9.26s (flat, cum) 9.17% of Total . . 74:func PkgIDFromPkgPath(path string) PkgID { 220ms 9.26s 75: return PkgID{HashBytes([]byte(path))} . . 76:} . . 77: . . 78:// Returns the ObjectID of the PackageValue associated with path. . . 79:func ObjectIDFromPkgPath(path string) ObjectID { . . 80: pkgID := PkgIDFromPkgPath(path) ``` * After ```shell (pprof) list PkgIDFromPkgPath Total: 93.22s ROUTINE ======================== github.com/gnolang/gno/gnovm/pkg/gnolang.PkgIDFromPkgPath in $PATH 210ms 1.55s (flat, cum) 1.66% of Total 50ms 50ms 78:func PkgIDFromPkgPath(path string) PkgID { . 490ms 79: pkgIDMu.Lock() 10ms 10ms 80: defer pkgIDMu.Unlock() . . 81: 10ms 730ms 82: pkgID, ok := pkgIDFromPkgPathCache[path] . . 83: if !ok { . . 84: pkgID = new(PkgID) . . 85: *pkgID = PkgID{HashBytes([]byte(path))} . . 86: pkgIDFromPkgPathCache[path] = pkgID . . 87: } 140ms 270ms 88: return *pkgID . . 89:} . . 90: . . 91:// Returns the ObjectID of the PackageValue associated with path. . . 92:func ObjectIDFromPkgPath(path string) ObjectID { . . 93: pkgID := PkgIDFromPkgPath(path) ``` Fixes #3423 --- gnovm/pkg/gnolang/bench_test.go | 31 +++++++++++++++++++++++++++++++ gnovm/pkg/gnolang/realm.go | 20 +++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 gnovm/pkg/gnolang/bench_test.go diff --git a/gnovm/pkg/gnolang/bench_test.go b/gnovm/pkg/gnolang/bench_test.go new file mode 100644 index 00000000000..b638ab66cd0 --- /dev/null +++ b/gnovm/pkg/gnolang/bench_test.go @@ -0,0 +1,31 @@ +package gnolang + +import ( + "testing" +) + +var sink any = nil + +var pkgIDPaths = []string{ + "encoding/json", + "math/bits", + "github.com/gnolang/gno/gnovm/pkg/gnolang", + "a", + " ", + "", + "github.com/gnolang/gno/gnovm/pkg/gnolang/vendor/pkg/github.com/gnolang/vendored", +} + +func BenchmarkPkgIDFromPkgPath(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + for _, path := range pkgIDPaths { + sink = PkgIDFromPkgPath(path) + } + } + + if sink == nil { + b.Fatal("Benchmark did not run!") + } + sink = nil +} diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index d822eb290eb..4100d9dace8 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -6,6 +6,7 @@ import ( "fmt" "reflect" "strings" + "sync" bm "github.com/gnolang/gno/gnovm/pkg/benchops" ) @@ -71,8 +72,25 @@ func (pid PkgID) Bytes() []byte { return pid.Hashlet[:] } +var ( + pkgIDMu sync.Mutex // pkgIDMu protects the shared cache. + // TODO: later on switch this to an LRU if needed to ensure + // fixed memory caps. For now though it isn't a problem: + // https://github.com/gnolang/gno/pull/3424#issuecomment-2564571785 + pkgIDFromPkgPathCache = make(map[string]*PkgID, 100) +) + func PkgIDFromPkgPath(path string) PkgID { - return PkgID{HashBytes([]byte(path))} + pkgIDMu.Lock() + defer pkgIDMu.Unlock() + + pkgID, ok := pkgIDFromPkgPathCache[path] + if !ok { + pkgID = new(PkgID) + *pkgID = PkgID{HashBytes([]byte(path))} + pkgIDFromPkgPathCache[path] = pkgID + } + return *pkgID } // Returns the ObjectID of the PackageValue associated with path.