Skip to content

Commit

Permalink
perf: optimize math.Int.Size for small values
Browse files Browse the repository at this point in the history
This is a simpler version of cosmos#16263 that only optimizes values that fit
in 53 bits. It is possible to optimize values that fit in 64 bits by
using big.Int.BitLen and math.Log2(10), but it doesn't seem worth the
complexity, especially given the revert of cosmos#16263.

I failed to beat big.Int.Marshal for values that don't fit in 64 bits.
  • Loading branch information
elias-orijtech committed Aug 22, 2023
1 parent 393dcc1 commit b797e0c
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 5 deletions.
1 change: 1 addition & 0 deletions math/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Ref: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.j

* [#17109](https://github.com/cosmos/cosmos-sdk/pull/17109) Add `.ToLegacyDec()` method on `math.Int` type for converting to `math.LegacyDec`.
* [#16263](https://github.com/cosmos/cosmos-sdk/pull/16263) Improved `math/Int.Size` by computing the decimal digits count instead of firstly invoking .Marshal() then checking the length
* [#17497](https://github.com/cosmos/cosmos-sdk/pull/17497) Optimize math.Int.Size for values that fit in 53 bits.

### Bug Fixes

Expand Down
12 changes: 12 additions & 0 deletions math/fuzz_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
})
}
19 changes: 19 additions & 0 deletions math/int.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding"
"encoding/json"
"fmt"
stdmath "math"
"math/big"
"strings"
"sync"
Expand Down Expand Up @@ -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)
}
Expand Down
12 changes: 7 additions & 5 deletions math/int_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down

0 comments on commit b797e0c

Please sign in to comment.