perf: math: make Int.Size() faster by computation not len(MarshalledBytes) (#16263)
Co-authored-by: marbar3778 <marbar3778@yahoo.com>
This commit is contained in:
co-authored by
marbar3778
parent
5235593e72
commit
9b9e319d1a
@@ -44,6 +44,8 @@ Ref: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.j
|
||||
|
||||
### Improvements
|
||||
|
||||
* [#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
|
||||
|
||||
* [#15768](https://github.com/cosmos/cosmos-sdk/pull/15768) Removed the second call to the `init` method for the global variable `grand`.
|
||||
* [#16141](https://github.com/cosmos/cosmos-sdk/pull/16141) Speedup `LegacyDec.ApproxRoot` and `LegacyDec.ApproxSqrt`.
|
||||
|
||||
|
||||
+84
-3
@@ -4,6 +4,7 @@ import (
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
stdmath "math"
|
||||
"math/big"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -421,9 +422,89 @@ func (i *Int) Unmarshal(data []byte) error {
|
||||
}
|
||||
|
||||
// Size implements the gogo proto custom type interface.
|
||||
func (i *Int) Size() int {
|
||||
bz, _ := i.Marshal()
|
||||
return len(bz)
|
||||
// Reduction power of 10 is the smallest power of 10, than 1<<64-1
|
||||
//
|
||||
// 18446744073709551615
|
||||
//
|
||||
// and the next value fitting with the digits of (1<<64)-1 is:
|
||||
//
|
||||
// 10000000000000000000
|
||||
var (
|
||||
big10Pow19, _ = new(big.Int).SetString("1"+strings.Repeat("0", 19), 10)
|
||||
log10Of2 = stdmath.Log10(2)
|
||||
)
|
||||
|
||||
func (i *Int) Size() (size int) {
|
||||
sign := i.Sign()
|
||||
if sign == 0 { // It is zero.
|
||||
// log*(0) is undefined hence return early.
|
||||
return 1
|
||||
}
|
||||
|
||||
ii := i.i
|
||||
alreadyMadeCopy := false
|
||||
if sign < 0 { // Negative sign encountered, so consider len("-")
|
||||
// The reason that we make this comparison in here is to
|
||||
// allow checking for negatives exactly once, to reduce
|
||||
// on comparisons inside sizeBigInt, hence we make a copy
|
||||
// of ii and make it absolute having taken note of the sign
|
||||
// already.
|
||||
size++
|
||||
// We already accounted for the negative sign above, thus
|
||||
// we can now compute the length of the absolute value.
|
||||
ii = new(big.Int).Abs(ii)
|
||||
alreadyMadeCopy = true
|
||||
}
|
||||
|
||||
// From here on, we are now dealing with non-0, non-negative values.
|
||||
return size + sizeBigInt(ii, alreadyMadeCopy)
|
||||
}
|
||||
|
||||
func sizeBigInt(i *big.Int, alreadyMadeCopy bool) (size int) {
|
||||
// This code assumes that non-0, non-negative values have been passed in.
|
||||
bitLen := i.BitLen()
|
||||
|
||||
res := float64(bitLen) * log10Of2
|
||||
ires := int(res)
|
||||
if diff := res - float64(ires); diff == 0.0 {
|
||||
return size + ires
|
||||
} else if diff >= 0.3 { // There are other digits past the bitLen, this is a heuristic.
|
||||
return size + ires + 1
|
||||
}
|
||||
|
||||
// Use Log10(x) for values less than (1<<64)-1, given it is only defined for [1, (1<<64)-1]
|
||||
if bitLen <= 64 {
|
||||
return size + 1 + int(stdmath.Log10(float64(i.Uint64())))
|
||||
}
|
||||
// Past this point, the value is greater than (1<<64)-1 and 10^19.
|
||||
|
||||
// The prior above computation of i.BitLen() * log10Of2 is inaccurate for powers of 10
|
||||
// and values like "9999999999999999999999999999"; that computation always overshoots by 1
|
||||
// hence our next alternative is to just go old school and keep dividing the value by:
|
||||
// 10^19 aka "10000000000000000000" while incrementing size += 19
|
||||
|
||||
// At this point we should just keep reducing by 10^19 as that's the smallest multiple
|
||||
// of 10 that matches the digit length of (1<<64)-1
|
||||
var ri *big.Int
|
||||
if alreadyMadeCopy {
|
||||
ri = i
|
||||
} else {
|
||||
ri = new(big.Int).Set(i)
|
||||
alreadyMadeCopy = true
|
||||
}
|
||||
|
||||
for ri.Cmp(big10Pow19) >= 0 { // Keep reducing the value by 10^19 and increment size by 19
|
||||
ri = ri.Quo(ri, big10Pow19)
|
||||
size += 19
|
||||
}
|
||||
|
||||
if ri.Sign() == 0 { // if the value is zero, no need for the recursion, just return immediately
|
||||
return size
|
||||
}
|
||||
|
||||
// Otherwise we already know how many times we reduced the value, so its
|
||||
// remnants less than 10^19 and those can be computed by again calling sizeBigInt.
|
||||
return size + sizeBigInt(ri, alreadyMadeCopy)
|
||||
}
|
||||
|
||||
// Override Amino binary serialization by proxying to protobuf.
|
||||
|
||||
@@ -513,3 +513,61 @@ func TestFormatIntCorrectness(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var sizeTests = []struct {
|
||||
s string
|
||||
want int
|
||||
}{
|
||||
{"0", 1},
|
||||
{"-0", 1},
|
||||
{"-10", 3},
|
||||
{"-10000", 6},
|
||||
{"10000", 5},
|
||||
{"100000", 6},
|
||||
{"99999", 5},
|
||||
{"10000000000", 11},
|
||||
{"18446744073709551616", 20},
|
||||
{"18446744073709551618", 20},
|
||||
{"184467440737095516181", 21},
|
||||
{"100000000000000000000000", 24},
|
||||
{"1000000000000000000000000000", 28},
|
||||
{"9000000000099999999999999999", 28},
|
||||
{"9999999999999999999999999999", 28},
|
||||
{"9903520314283042199192993792", 28},
|
||||
{"340282366920938463463374607431768211456", 39},
|
||||
{"3402823669209384634633746074317682114569999", 43},
|
||||
{"9999999999999999999999999999999999999999999", 43},
|
||||
{"99999999999999999999999999999999999999999999", 44},
|
||||
{"999999999999999999999999999999999999999999999", 45},
|
||||
{"90000000000999999999999999999000000000099999999999999999", 56},
|
||||
{"-90000000000999999999999999999000000000099999999999999999", 57},
|
||||
{"9000000000099999999999999999900000000009999999999999999990", 58},
|
||||
{"990000000009999999999999999990000000000999999999999999999999", 60},
|
||||
{"99000000000999999999999999999000000000099999999999999999999919", 62},
|
||||
{"90000000000999999990000000000000000000000000000000000000000000", 62},
|
||||
{"99999999999999999999999999990000000000000000000000000000000000", 62},
|
||||
{"11111111111111119999999999990000000000000000000000000000000000", 62},
|
||||
{"99000000000999999999999999999000000000099999999999999999999919", 62},
|
||||
{"10000000000000000000000000000000000000000000000000000000000000", 62},
|
||||
{"10000000000000000000000000000000000000000000000000000000000000000000000000000", 77},
|
||||
{"99999999999999999999999999999999999999999999999999999999999999999999999999999", 77},
|
||||
{"110000000000000000000000000000000000000000000000000000000000000000000000000009", 78},
|
||||
}
|
||||
|
||||
func BenchmarkIntSize(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, st := range sizeTests {
|
||||
ii, _ := math.NewIntFromString(st.s)
|
||||
got := ii.Size()
|
||||
if got != st.want {
|
||||
b.Errorf("%q:: got=%d, want=%d", st.s, got, st.want)
|
||||
}
|
||||
sink = got
|
||||
}
|
||||
}
|
||||
if sink == nil {
|
||||
b.Fatal("Benchmark did not run!")
|
||||
}
|
||||
sink = nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user