Skip to content

Commit

Permalink
perf(gnovm): cache PkgIDFromPkgPath for higher performance (#3424)
Browse files Browse the repository at this point in the history
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

---------

Co-authored-by: Nathan Toups <[email protected]>
Co-authored-by: Morgan Bazalgette <[email protected]>
  • Loading branch information
3 people authored Jan 9, 2025
1 parent 41f8763 commit e5b9095
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 1 deletion.
31 changes: 31 additions & 0 deletions gnovm/pkg/gnolang/bench_test.go
Original file line number Diff line number Diff line change
@@ -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
}
20 changes: 19 additions & 1 deletion gnovm/pkg/gnolang/realm.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"reflect"
"strings"
"sync"

bm "github.com/gnolang/gno/gnovm/pkg/benchops"
)
Expand Down Expand Up @@ -71,8 +72,25 @@ func (pid PkgID) Bytes() []byte {
return pid.Hashlet[:]
}

var (
pkgIDFromPkgPathCacheMu sync.Mutex // 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))}
pkgIDFromPkgPathCacheMu.Lock()
defer pkgIDFromPkgPathCacheMu.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.
Expand Down

0 comments on commit e5b9095

Please sign in to comment.