perf: Speedup sdk.Int overflow checks (#19386)

This commit is contained in:
Dev Ojha 2024-02-09 02:51:11 -06:00 committed by GitHub
parent 6e8de00f77
commit ac48269f98
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 51 additions and 9 deletions

View File

@ -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

View File

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"math/big"
"math/bits"
"strings"
"sync"
"testing"
@ -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
@ -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)
}
@ -128,7 +135,7 @@ func NewIntFromBigInt(i *big.Int) Int {
return Int{}
}
if i.BitLen() > MaxBitLen {
if bigIntOverflows(i) {
panic("NewIntFromBigInt() out of bound")
}
@ -143,7 +150,7 @@ func NewIntFromBigIntMut(i *big.Int) Int {
return Int{}
}
if i.BitLen() > MaxBitLen {
if bigIntOverflows(i) {
panic("NewIntFromBigInt() out of bound")
}
@ -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
}
@ -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}
@ -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
@ -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
@ -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
@ -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)
}
@ -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
}

View File

@ -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
}