Skip to content

Commit

Permalink
perf: Speedup sdk.Int overflow checks (#19386)
Browse files Browse the repository at this point in the history
  • Loading branch information
ValarDragon authored Feb 9, 2024
1 parent 6e8de00 commit ac48269
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 9 deletions.
1 change: 1 addition & 0 deletions math/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Ref: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.j
* [#18421](https://github.com/cosmos/cosmos-sdk/pull/18421) Add mutative api for `LegacyDec.BigInt()`.

* [#18874](https://github.com/cosmos/cosmos-sdk/pull/18874) Speedup `math.Int.Mul` by removing a duplicate overflow check
* [#19386](https://github.com/cosmos/cosmos-sdk/pull/19386) Speedup `math.Int` overflow checks

### Bug Fixes

Expand Down
37 changes: 28 additions & 9 deletions math/int.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"math/big"
"math/bits"
"strings"
"sync"
"testing"
Expand All @@ -14,6 +15,12 @@ import (
// MaxBitLen defines the maximum bit length supported bit Int and Uint types.
const MaxBitLen = 256

// maxWordLen defines the maximum word length supported by Int and Uint types.
// We check overflow, by first doing a fast check if the word length is below maxWordLen
// and if not then do the slower full bitlen check.
// NOTE: If MaxBitLen is not a multiple of bits.UintSize, then we need to edit the used logic slightly.
const maxWordLen = MaxBitLen / bits.UintSize

// Integer errors
var (
// ErrIntOverflow is the error returned when an integer overflow occurs
Expand Down Expand Up @@ -71,7 +78,7 @@ func unmarshalText(i *big.Int, text string) error {
return err
}

if i.BitLen() > MaxBitLen {
if bigIntOverflows(i) {
return fmt.Errorf("integer out of range: %s", text)
}

Expand Down Expand Up @@ -128,7 +135,7 @@ func NewIntFromBigInt(i *big.Int) Int {
return Int{}
}

if i.BitLen() > MaxBitLen {
if bigIntOverflows(i) {
panic("NewIntFromBigInt() out of bound")
}

Expand All @@ -143,7 +150,7 @@ func NewIntFromBigIntMut(i *big.Int) Int {
return Int{}
}

if i.BitLen() > MaxBitLen {
if bigIntOverflows(i) {
panic("NewIntFromBigInt() out of bound")
}

Expand All @@ -157,7 +164,7 @@ func NewIntFromString(s string) (res Int, ok bool) {
return
}
// Check overflow
if i.BitLen() > MaxBitLen {
if bigIntOverflows(i) {
ok = false
return
}
Expand All @@ -175,7 +182,7 @@ func NewIntWithDecimal(n int64, dec int) Int {
i.Mul(big.NewInt(n), exp)

// Check overflow
if i.BitLen() > MaxBitLen {
if bigIntOverflows(i) {
panic("NewIntWithDecimal() out of bound")
}
return Int{i}
Expand Down Expand Up @@ -285,7 +292,7 @@ func (i Int) AddRaw(i2 int64) Int {
func (i Int) SafeAdd(i2 Int) (res Int, err error) {
res = Int{add(i.i, i2.i)}
// Check overflow
if res.i.BitLen() > MaxBitLen {
if bigIntOverflows(res.i) {
return Int{}, ErrIntOverflow
}
return res, nil
Expand All @@ -310,7 +317,7 @@ func (i Int) SubRaw(i2 int64) Int {
func (i Int) SafeSub(i2 Int) (res Int, err error) {
res = Int{sub(i.i, i2.i)}
// Check overflow/underflow
if res.i.BitLen() > MaxBitLen {
if bigIntOverflows(res.i) {
return Int{}, ErrIntOverflow
}
return res, nil
Expand All @@ -335,7 +342,7 @@ func (i Int) MulRaw(i2 int64) Int {
func (i Int) SafeMul(i2 Int) (res Int, err error) {
res = Int{mul(i.i, i2.i)}
// Check overflow
if res.i.BitLen() > MaxBitLen {
if bigIntOverflows(res.i) {
return Int{}, ErrIntOverflow
}
return res, nil
Expand Down Expand Up @@ -497,7 +504,7 @@ func (i *Int) Unmarshal(data []byte) error {
return err
}

if i.i.BitLen() > MaxBitLen {
if bigIntOverflows(i.i) {
return fmt.Errorf("integer out of range; got: %d, max: %d", i.i.BitLen(), MaxBitLen)
}

Expand Down Expand Up @@ -591,3 +598,15 @@ func FormatInt(v string) (string, error) {

return sign + sb.String(), nil
}

// check if the big int overflows.
func bigIntOverflows(i *big.Int) bool {
// overflow is defined as i.BitLen() > MaxBitLen
// however this check can be expensive when doing many operations.
// So we first check if the word length is greater than maxWordLen.
// However the most significant word could be zero, hence we still do the bitlen check.
if len(i.Bits()) > maxWordLen {
return i.BitLen() > MaxBitLen
}
return false
}
22 changes: 22 additions & 0 deletions math/int_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -687,3 +687,25 @@ func BenchmarkIntSize(b *testing.B) {
}
sink = nil
}

func BenchmarkIntOverflowCheckTime(b *testing.B) {
var ints = []*big.Int{}

for _, st := range sizeTests {
ii, _ := math.NewIntFromString(st.s)
ints = append(ints, ii.BigInt())
}
b.ResetTimer()

b.ReportAllocs()
for i := 0; i < b.N; i++ {
for j, _ := range sizeTests {
got := math.NewIntFromBigIntMut(ints[j])
sink = got
}
}
if sink == nil {
b.Fatal("Benchmark did not run!")
}
sink = nil
}

0 comments on commit ac48269

Please sign in to comment.