diff --git a/math/CHANGELOG.md b/math/CHANGELOG.md index 4dadc5f0d76c..06acc1a037cd 100644 --- a/math/CHANGELOG.md +++ b/math/CHANGELOG.md @@ -34,6 +34,12 @@ Ref: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.j # Changelog +## [Unreleased] + +### Improvements + +* [#17497](https://github.com/cosmos/cosmos-sdk/pull/17497) Optimize math.Int.Size for values that fit in 53 bits. + ## [math/v1.1.2](https://github.com/cosmos/cosmos-sdk/releases/tag/math/v1.1.2) - 2023-08-21 ### Bug Fixes diff --git a/math/fuzz_test.go b/math/fuzz_test.go index e50ae41bfa13..5d82099b795d 100644 --- a/math/fuzz_test.go +++ b/math/fuzz_test.go @@ -22,3 +22,15 @@ func FuzzLegacyNewDecFromStr(f *testing.F) { } }) } + +func FuzzSmallIntSize(f *testing.F) { + f.Add(int64(2<<53 - 1)) + f.Add(-int64(2<<53 - 1)) + f.Fuzz(func(t *testing.T, input int64) { + i := NewInt(input) + exp, _ := i.Marshal() + if i.Size() != len(exp) { + t.Fatalf("input %d: i.Size()=%d, len(input)=%d", input, i.Size(), len(exp)) + } + }) +} diff --git a/math/int.go b/math/int.go index 1b10781b95f0..edcb38ca7407 100644 --- a/math/int.go +++ b/math/int.go @@ -4,6 +4,7 @@ import ( "encoding" "encoding/json" "fmt" + stdmath "math" "math/big" "strings" "sync" @@ -429,6 +430,24 @@ func (i *Int) Unmarshal(data []byte) error { // Size implements the gogo proto custom type interface. func (i *Int) Size() int { + if i.i == nil { + return 1 + } + // A float64 can store 52 bits exactly, which allows us to use + // math.Log10 to compute the size fast and garbage free. + if i.i.BitLen() <= 52 { + i64 := i.i.Int64() + if i64 == 0 { + return 1 + } + size := 0 + if i64 < 0 { + i64 = -i64 + size++ + } + return size + 1 + int(stdmath.Log10(float64(i64))) + } + // Slow path. bz, _ := i.Marshal() return len(bz) } diff --git a/math/int_test.go b/math/int_test.go index 4da1cae42336..dbfebba40677 100644 --- a/math/int_test.go +++ b/math/int_test.go @@ -592,14 +592,16 @@ func TestNewIntFromString(t *testing.T) { } func BenchmarkIntSize(b *testing.B) { + var tests []math.Int + for _, st := range sizeTests { + ii, _ := math.NewIntFromString(st.s) + tests = append(tests, ii) + } + b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - for _, st := range sizeTests { - ii, _ := math.NewIntFromString(st.s) + for _, ii := range tests { got := ii.Size() - if got != st.want { - b.Errorf("%q:: got=%d, want=%d", st.s, got, st.want) - } sink = got } }