diff --git a/orm/encoding/ormfield/string.go b/orm/encoding/ormfield/string.go index 90c368f81f..3a9955e83e 100644 --- a/orm/encoding/ormfield/string.go +++ b/orm/encoding/ormfield/string.go @@ -16,7 +16,7 @@ func (s StringCodec) FixedBufferSize() int { } func (s StringCodec) ComputeBufferSize(value protoreflect.Value) (int, error) { - return len(value.Interface().(string)), nil + return len(value.String()), nil } func (s StringCodec) IsOrdered() bool { @@ -24,7 +24,7 @@ func (s StringCodec) IsOrdered() bool { } func (s StringCodec) Compare(v1, v2 protoreflect.Value) int { - return strings.Compare(v1.Interface().(string), v2.Interface().(string)) + return strings.Compare(v1.String(), v2.String()) } func (s StringCodec) Decode(r Reader) (protoreflect.Value, error) { @@ -33,7 +33,7 @@ func (s StringCodec) Decode(r Reader) (protoreflect.Value, error) { } func (s StringCodec) Encode(value protoreflect.Value, w io.Writer) error { - _, err := w.Write([]byte(value.Interface().(string))) + _, err := w.Write([]byte(value.String())) return err } @@ -46,7 +46,7 @@ func (s NonTerminalStringCodec) FixedBufferSize() int { } func (s NonTerminalStringCodec) ComputeBufferSize(value protoreflect.Value) (int, error) { - return len(value.Interface().(string)) + 1, nil + return len(value.String()) + 1, nil } func (s NonTerminalStringCodec) IsOrdered() bool { @@ -54,7 +54,7 @@ func (s NonTerminalStringCodec) IsOrdered() bool { } func (s NonTerminalStringCodec) Compare(v1, v2 protoreflect.Value) int { - return strings.Compare(v1.Interface().(string), v2.Interface().(string)) + return strings.Compare(v1.String(), v2.String()) } func (s NonTerminalStringCodec) Decode(r Reader) (protoreflect.Value, error) { @@ -69,7 +69,7 @@ func (s NonTerminalStringCodec) Decode(r Reader) (protoreflect.Value, error) { } func (s NonTerminalStringCodec) Encode(value protoreflect.Value, w io.Writer) error { - str := value.Interface().(string) + str := value.String() bz := []byte(str) for _, b := range bz { if b == 0 { diff --git a/orm/encoding/ormkv/key_codec.go b/orm/encoding/ormkv/key_codec.go index 9b3605b5ff..00ef51ea28 100644 --- a/orm/encoding/ormkv/key_codec.go +++ b/orm/encoding/ormkv/key_codec.go @@ -82,7 +82,7 @@ func (cdc *KeyCodec) EncodeKey(values []protoreflect.Value) ([]byte, error) { return nil, err } - w := bytes.NewBuffer(make([]byte, 0, sz)) + w := bytes.NewBuffer(make([]byte, 0, sz+len(cdc.prefix))) if _, err = w.Write(cdc.prefix); err != nil { return nil, err } diff --git a/orm/internal/testkv/leveldb.go b/orm/internal/testkv/leveldb.go new file mode 100644 index 0000000000..e7fb201e0a --- /dev/null +++ b/orm/internal/testkv/leveldb.go @@ -0,0 +1,18 @@ +package testkv + +import ( + "testing" + + dbm "github.com/tendermint/tm-db" + "gotest.tools/v3/assert" + + "github.com/cosmos/cosmos-sdk/orm/model/ormtable" +) + +func NewGoLevelDBBackend(t testing.TB) ormtable.Backend { + db, err := dbm.NewGoLevelDB("test", t.TempDir()) + assert.NilError(t, err) + return ormtable.NewBackend(ormtable.BackendOptions{ + CommitmentStore: db, + }) +} diff --git a/orm/model/ormtable/bench_test.go b/orm/model/ormtable/bench_test.go new file mode 100644 index 0000000000..d160da700f --- /dev/null +++ b/orm/model/ormtable/bench_test.go @@ -0,0 +1,315 @@ +package ormtable_test + +import ( + "context" + "fmt" + "testing" + + "google.golang.org/protobuf/proto" + + "github.com/cosmos/cosmos-sdk/orm/internal/testkv" + "github.com/cosmos/cosmos-sdk/orm/testing/ormtest" + + dbm "github.com/tendermint/tm-db" + "gotest.tools/v3/assert" + + "github.com/cosmos/cosmos-sdk/orm/internal/testpb" + "github.com/cosmos/cosmos-sdk/orm/model/ormtable" + "github.com/cosmos/cosmos-sdk/orm/types/kv" +) + +func initBalanceTable(t testing.TB) testpb.BalanceTable { + table, err := ormtable.Build(ormtable.Options{ + MessageType: (&testpb.Balance{}).ProtoReflect().Type(), + }) + assert.NilError(t, err) + + balanceTable, err := testpb.NewBalanceTable(table) + assert.NilError(t, err) + + return balanceTable +} + +func BenchmarkMemory(b *testing.B) { + bench(b, func(tb testing.TB) ormtable.Backend { + return ormtest.NewMemoryBackend() + }) +} + +func BenchmarkLevelDB(b *testing.B) { + bench(b, testkv.NewGoLevelDBBackend) +} + +func bench(b *testing.B, newBackend func(testing.TB) ormtable.Backend) { + b.Run("insert", func(b *testing.B) { + b.StopTimer() + ctx := ormtable.WrapContextDefault(newBackend(b)) + b.StartTimer() + benchInsert(b, ctx) + }) + b.Run("update", func(b *testing.B) { + b.StopTimer() + ctx := ormtable.WrapContextDefault(newBackend(b)) + benchInsert(b, ctx) + b.StartTimer() + benchUpdate(b, ctx) + }) + b.Run("get", func(b *testing.B) { + b.StopTimer() + ctx := ormtable.WrapContextDefault(newBackend(b)) + benchInsert(b, ctx) + b.StartTimer() + benchGet(b, ctx) + }) + b.Run("delete", func(b *testing.B) { + b.StopTimer() + ctx := ormtable.WrapContextDefault(newBackend(b)) + benchInsert(b, ctx) + b.StartTimer() + benchDelete(b, ctx) + }) +} + +func benchInsert(b *testing.B, ctx context.Context) { + balanceTable := initBalanceTable(b) + for i := 0; i < b.N; i++ { + assert.NilError(b, balanceTable.Insert(ctx, &testpb.Balance{ + Address: fmt.Sprintf("acct%d", i), + Denom: "bar", + Amount: 10, + })) + } +} + +func benchUpdate(b *testing.B, ctx context.Context) { + balanceTable := initBalanceTable(b) + for i := 0; i < b.N; i++ { + assert.NilError(b, balanceTable.Update(ctx, &testpb.Balance{ + Address: fmt.Sprintf("acct%d", i), + Denom: "bar", + Amount: 11, + })) + } +} + +func benchGet(b *testing.B, ctx context.Context) { + balanceTable := initBalanceTable(b) + for i := 0; i < b.N; i++ { + balance, err := balanceTable.Get(ctx, fmt.Sprintf("acct%d", i), "bar") + assert.NilError(b, err) + assert.Equal(b, uint64(10), balance.Amount) + } +} + +func benchDelete(b *testing.B, ctx context.Context) { + balanceTable := initBalanceTable(b) + for i := 0; i < b.N; i++ { + assert.NilError(b, balanceTable.Delete(ctx, &testpb.Balance{ + Address: fmt.Sprintf("acct%d", i), + Denom: "bar", + })) + } +} + +// +// Manually written versions of insert, update, delete and get for testpb.Balance +// + +const ( + addressDenomPrefix byte = iota + denomAddressPrefix +) + +func insertBalance(store kv.Store, balance *testpb.Balance) error { + denom := balance.Denom + balance.Denom = "" + addr := balance.Address + balance.Address = "" + + addressDenomKey := []byte{addressDenomPrefix} + addressDenomKey = append(addressDenomKey, []byte(addr)...) + addressDenomKey = append(addressDenomKey, 0x0) + addressDenomKey = append(addressDenomKey, []byte(denom)...) + has, err := store.Has(addressDenomKey) + if err != nil { + return err + } + + if has { + return fmt.Errorf("already exists") + } + + bz, err := proto.Marshal(balance) + if err != nil { + return err + } + balance.Denom = denom + balance.Address = addr + + err = store.Set(addressDenomKey, bz) + if err != nil { + return err + } + + // set denom address index + denomAddressKey := []byte{denomAddressPrefix} + denomAddressKey = append(denomAddressKey, []byte(balance.Denom)...) + denomAddressKey = append(denomAddressKey, 0x0) + denomAddressKey = append(denomAddressKey, []byte(balance.Address)...) + err = store.Set(denomAddressKey, []byte{}) + if err != nil { + return err + } + + return nil +} + +func updateBalance(store kv.Store, balance *testpb.Balance) error { + denom := balance.Denom + balance.Denom = "" + addr := balance.Address + balance.Address = "" + bz, err := proto.Marshal(balance) + if err != nil { + return err + } + balance.Denom = denom + balance.Address = addr + + addressDenomKey := []byte{addressDenomPrefix} + addressDenomKey = append(addressDenomKey, []byte(addr)...) + addressDenomKey = append(addressDenomKey, 0x0) + addressDenomKey = append(addressDenomKey, []byte(denom)...) + + return store.Set(addressDenomKey, bz) +} + +func deleteBalance(store kv.Store, balance *testpb.Balance) error { + denom := balance.Denom + addr := balance.Address + + addressDenomKey := []byte{addressDenomPrefix} + addressDenomKey = append(addressDenomKey, []byte(addr)...) + addressDenomKey = append(addressDenomKey, 0x0) + addressDenomKey = append(addressDenomKey, []byte(denom)...) + err := store.Delete(addressDenomKey) + if err != nil { + return err + } + + denomAddressKey := []byte{denomAddressPrefix} + denomAddressKey = append(denomAddressKey, []byte(balance.Denom)...) + denomAddressKey = append(denomAddressKey, 0x0) + denomAddressKey = append(denomAddressKey, []byte(balance.Address)...) + return store.Delete(denomAddressKey) +} + +func getBalance(store kv.Store, address, denom string) (*testpb.Balance, error) { + addressDenomKey := []byte{addressDenomPrefix} + addressDenomKey = append(addressDenomKey, []byte(address)...) + addressDenomKey = append(addressDenomKey, 0x0) + addressDenomKey = append(addressDenomKey, []byte(denom)...) + + bz, err := store.Get(addressDenomKey) + if err != nil { + return nil, err + } + + if bz == nil { + return nil, fmt.Errorf("not found") + } + + var balance = testpb.Balance{} + err = proto.Unmarshal(bz, &balance) + if err != nil { + return nil, err + } + + balance.Address = address + balance.Denom = denom + + return &balance, nil +} + +func BenchmarkManualInsertMemory(b *testing.B) { + benchManual(b, func() (dbm.DB, error) { + return dbm.NewMemDB(), nil + }) +} + +func BenchmarkManualInsertLevelDB(b *testing.B) { + benchManual(b, func() (dbm.DB, error) { + return dbm.NewGoLevelDB("test", b.TempDir()) + }) +} + +func benchManual(b *testing.B, newStore func() (dbm.DB, error)) { + b.Run("insert", func(b *testing.B) { + b.StopTimer() + store, err := newStore() + assert.NilError(b, err) + b.StartTimer() + benchManualInsert(b, store) + }) + b.Run("update", func(b *testing.B) { + b.StopTimer() + store, err := newStore() + assert.NilError(b, err) + benchManualInsert(b, store) + b.StartTimer() + benchManualUpdate(b, store) + }) + b.Run("get", func(b *testing.B) { + b.StopTimer() + store, err := newStore() + assert.NilError(b, err) + benchManualInsert(b, store) + b.StartTimer() + benchManualGet(b, store) + }) + b.Run("delete", func(b *testing.B) { + b.StopTimer() + store, err := newStore() + assert.NilError(b, err) + benchManualInsert(b, store) + b.StartTimer() + benchManualDelete(b, store) + }) +} + +func benchManualInsert(b *testing.B, store kv.Store) { + for i := 0; i < b.N; i++ { + assert.NilError(b, insertBalance(store, &testpb.Balance{ + Address: fmt.Sprintf("acct%d", i), + Denom: "bar", + Amount: 10, + })) + } +} + +func benchManualUpdate(b *testing.B, store kv.Store) { + for i := 0; i < b.N; i++ { + assert.NilError(b, updateBalance(store, &testpb.Balance{ + Address: fmt.Sprintf("acct%d", i), + Denom: "bar", + Amount: 11, + })) + } +} + +func benchManualDelete(b *testing.B, store kv.Store) { + for i := 0; i < b.N; i++ { + assert.NilError(b, deleteBalance(store, &testpb.Balance{ + Address: fmt.Sprintf("acct%d", i), + Denom: "bar", + })) + } +} + +func benchManualGet(b *testing.B, store kv.Store) { + for i := 0; i < b.N; i++ { + balance, err := getBalance(store, fmt.Sprintf("acct%d", i), "bar") + assert.NilError(b, err) + assert.Equal(b, uint64(10), balance.Amount) + } +}