From bf13cb33cf7a5005764d63f1b426cf703a7db0ac Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Thu, 1 Apr 2021 17:40:34 -0700 Subject: [PATCH 1/5] Add optimization for sqrt --- x/gamm/keeper/math.go | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/x/gamm/keeper/math.go b/x/gamm/keeper/math.go index 1f0ccfc37c8..1e232e10e4e 100644 --- a/x/gamm/keeper/math.go +++ b/x/gamm/keeper/math.go @@ -208,18 +208,18 @@ func pow(base sdk.Dec, exp sdk.Dec) sdk.Dec { // You can see this by recalling that `i = (-1)^(.5)`. We have to go to complex numbers to define this. // (And would have to implement complex logarithms) // We don't have a need for negative bases, so we don't include any such logic. - if base.LTE(sdk.ZeroDec()) { - panic(fmt.Errorf("base have to be greater than zero")) + if !base.IsPositive() { + panic(fmt.Errorf("base must be greater than 0")) } - // TODO: Remove this, we can do a + // TODO: Remove this if we want to generalize the function, + // we can adjust the algorithm in this setting. if base.GTE(sdk.OneDec().MulInt64(2)) { - panic(fmt.Errorf("base have to be lesser than two")) + panic(fmt.Errorf("base must be lesser than two")) } // We will use an approximation algorithm to compute the power. // Since computing an integer power is easy, we split up the exponent into // an integer component and a fractional component. - // a integer := exp.TruncateDec() fractional := exp.Sub(integer) @@ -234,13 +234,24 @@ func pow(base sdk.Dec, exp sdk.Dec) sdk.Dec { return integerPow.Mul(partialResult) } +var one_half sdk.Dec = sdk.MustNewDecFromStr("0.5") + +// Contract: 0 < base < 2 func powApprox(base sdk.Dec, exp sdk.Dec, precision sdk.Dec) sdk.Dec { - if base.LTE(sdk.ZeroDec()) { - panic(fmt.Errorf("base must be greater than zero")) + if exp.IsZero() { + return sdk.ZeroDec() } - if base.GTE(sdk.OneDec().MulInt64(2)) { - panic(fmt.Errorf("base must be less than two")) + + // Common case optimization + // Optimize for it being equal to one-half + if exp.Equal(one_half) { + output, err := base.ApproxSqrt() + if err != nil { + panic(err) + } + return output } + // TODO: Make an approx-equal function, and then check if exp * 3 = 1, and do a check accordingly a := exp x, xneg := subSign(base, sdk.OneDec()) From 602c1ce4197127957a3c6a0cffbdea03de919dcc Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Thu, 1 Apr 2021 17:40:44 -0700 Subject: [PATCH 2/5] Refactor pow benchmark to its own file --- x/gamm/keeper/math_test.go | 54 ----------------------------- x/gamm/keeper/pow_bench_test.go | 61 +++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 54 deletions(-) create mode 100644 x/gamm/keeper/pow_bench_test.go diff --git a/x/gamm/keeper/math_test.go b/x/gamm/keeper/math_test.go index 3f34253766e..1356aa9433c 100644 --- a/x/gamm/keeper/math_test.go +++ b/x/gamm/keeper/math_test.go @@ -272,57 +272,3 @@ func TestCalcPoolInGivenSingleOut(t *testing.T) { "expected value & actual value's difference should less than precision*10000", ) } - -func BenchmarkPow(b *testing.B) { - tests := []struct { - base sdk.Dec - exp sdk.Dec - }{ - { - base: sdk.NewDecWithPrec(12, 1), - exp: sdk.NewDecWithPrec(12, 1), - }, - { - base: sdk.NewDecWithPrec(5, 1), - exp: sdk.NewDecWithPrec(11122, 3), - }, - { - base: sdk.NewDecWithPrec(1, 1), - exp: sdk.NewDecWithPrec(492, 8), - }, - { - base: sdk.NewDecWithPrec(2423, 7), - exp: sdk.NewDecWithPrec(1213, 1), - }, - { - base: sdk.NewDecWithPrec(493, 3), - exp: sdk.NewDecWithPrec(121, 8), - }, - { - base: sdk.NewDecWithPrec(249, 6), - exp: sdk.NewDecWithPrec(2304, 1), - }, - { - base: sdk.NewDecWithPrec(2342, 4), - exp: sdk.NewDecWithPrec(322, 1), - }, - { - base: sdk.NewDecWithPrec(999, 6), - exp: sdk.NewDecWithPrec(1424, 1), - }, - { - base: sdk.NewDecWithPrec(1234, 3), - exp: sdk.NewDecWithPrec(1203, 1), - }, - { - base: sdk.NewDecWithPrec(122, 5), - exp: sdk.NewDecWithPrec(1232, 1), - }, - } - - for i := 0; i < b.N; i++ { - for _, test := range tests { - pow(test.base, test.exp) - } - } -} diff --git a/x/gamm/keeper/pow_bench_test.go b/x/gamm/keeper/pow_bench_test.go new file mode 100644 index 00000000000..9257e2b625e --- /dev/null +++ b/x/gamm/keeper/pow_bench_test.go @@ -0,0 +1,61 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func BenchmarkPow(b *testing.B) { + tests := []struct { + base sdk.Dec + exp sdk.Dec + }{ + { + base: sdk.NewDecFromStr(12, 1), + exp: sdk.NewDecWithPrec(12, 1), + }, + { + base: sdk.NewDecWithPrec(5, 1), + exp: sdk.NewDecWithPrec(11122, 3), + }, + { + base: sdk.NewDecWithPrec(1, 1), + exp: sdk.NewDecWithPrec(492, 8), + }, + { + base: sdk.NewDecWithPrec(2423, 7), + exp: sdk.NewDecWithPrec(1213, 1), + }, + { + base: sdk.NewDecWithPrec(493, 3), + exp: sdk.NewDecWithPrec(121, 8), + }, + { + base: sdk.NewDecWithPrec(249, 6), + exp: sdk.NewDecWithPrec(2304, 1), + }, + { + base: sdk.NewDecWithPrec(2342, 4), + exp: sdk.NewDecWithPrec(322, 1), + }, + { + base: sdk.NewDecWithPrec(999, 6), + exp: sdk.NewDecWithPrec(1424, 1), + }, + { + base: sdk.NewDecWithPrec(1234, 3), + exp: sdk.NewDecWithPrec(1203, 1), + }, + { + base: sdk.NewDecWithPrec(122, 5), + exp: sdk.NewDecWithPrec(1232, 1), + }, + } + + for i := 0; i < b.N; i++ { + for _, test := range tests { + pow(test.base, test.exp) + } + } +} From 3b6e83537b3462ad73100a5bd61df7dcdf1e5fe7 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Thu, 1 Apr 2021 17:43:34 -0700 Subject: [PATCH 3/5] Switch to string specification of dec's --- x/gamm/keeper/pow_bench_test.go | 41 +++++++++++++++++---------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/x/gamm/keeper/pow_bench_test.go b/x/gamm/keeper/pow_bench_test.go index 9257e2b625e..b713a1810fc 100644 --- a/x/gamm/keeper/pow_bench_test.go +++ b/x/gamm/keeper/pow_bench_test.go @@ -11,45 +11,46 @@ func BenchmarkPow(b *testing.B) { base sdk.Dec exp sdk.Dec }{ + // TODO: Choose selection here more robustly { - base: sdk.NewDecFromStr(12, 1), - exp: sdk.NewDecWithPrec(12, 1), + base: sdk.MustNewDecFromStr("1.2"), + exp: sdk.MustNewDecFromStr("1.2"), }, { - base: sdk.NewDecWithPrec(5, 1), - exp: sdk.NewDecWithPrec(11122, 3), + base: sdk.MustNewDecFromStr("0.5"), + exp: sdk.MustNewDecFromStr("11.122"), }, { - base: sdk.NewDecWithPrec(1, 1), - exp: sdk.NewDecWithPrec(492, 8), + base: sdk.MustNewDecFromStr("0.1"), + exp: sdk.MustNewDecFromStr("0.00000492"), }, { - base: sdk.NewDecWithPrec(2423, 7), - exp: sdk.NewDecWithPrec(1213, 1), + base: sdk.MustNewDecFromStr("0.0002423"), + exp: sdk.MustNewDecFromStr("0.1234"), }, { - base: sdk.NewDecWithPrec(493, 3), - exp: sdk.NewDecWithPrec(121, 8), + base: sdk.MustNewDecFromStr("0.493"), + exp: sdk.MustNewDecFromStr("0.00000121"), }, { - base: sdk.NewDecWithPrec(249, 6), - exp: sdk.NewDecWithPrec(2304, 1), + base: sdk.MustNewDecFromStr("0.000249"), + exp: sdk.MustNewDecFromStr("2.304"), }, { - base: sdk.NewDecWithPrec(2342, 4), - exp: sdk.NewDecWithPrec(322, 1), + base: sdk.MustNewDecFromStr("0.2342"), + exp: sdk.MustNewDecFromStr("32.2"), }, { - base: sdk.NewDecWithPrec(999, 6), - exp: sdk.NewDecWithPrec(1424, 1), + base: sdk.MustNewDecFromStr("0.000999"), + exp: sdk.MustNewDecFromStr("142.4"), }, { - base: sdk.NewDecWithPrec(1234, 3), - exp: sdk.NewDecWithPrec(1203, 1), + base: sdk.MustNewDecFromStr("1.234"), + exp: sdk.MustNewDecFromStr("120.3"), }, { - base: sdk.NewDecWithPrec(122, 5), - exp: sdk.NewDecWithPrec(1232, 1), + base: sdk.MustNewDecFromStr("0.00122"), + exp: sdk.MustNewDecFromStr("123.2"), }, } From 645068a6b14a8443f060e50e5e0fb31801cc9873 Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Thu, 1 Apr 2021 17:47:24 -0700 Subject: [PATCH 4/5] Add sqrt benchmark --- x/gamm/keeper/pow_bench_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/x/gamm/keeper/pow_bench_test.go b/x/gamm/keeper/pow_bench_test.go index b713a1810fc..0af73bb365c 100644 --- a/x/gamm/keeper/pow_bench_test.go +++ b/x/gamm/keeper/pow_bench_test.go @@ -60,3 +60,27 @@ func BenchmarkPow(b *testing.B) { } } } + +func BenchmarkSqrtPow(b *testing.B) { + tests := []struct { + base sdk.Dec + }{ + // TODO: Choose selection here more robustly + { + base: sdk.MustNewDecFromStr("1.29847"), + }, + { + base: sdk.MustNewDecFromStr("1.313135"), + }, + { + base: sdk.MustNewDecFromStr("1.65976735939"), + }, + } + one_half := sdk.MustNewDecFromStr("0.5") + + for i := 0; i < b.N; i++ { + for _, test := range tests { + pow(test.base, one_half) + } + } +} From e7ef7b441ab7d4f1325cc8d8ce74da1c5f1f0bfb Mon Sep 17 00:00:00 2001 From: ValarDragon Date: Fri, 2 Apr 2021 08:50:46 -0700 Subject: [PATCH 5/5] Remove allocations for constants that won't be replaced by in-place ops, rename subSign --- x/gamm/keeper/math.go | 24 +++++++++++++++--------- x/gamm/keeper/math_test.go | 4 ++-- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/x/gamm/keeper/math.go b/x/gamm/keeper/math.go index 1e232e10e4e..863abf371d6 100644 --- a/x/gamm/keeper/math.go +++ b/x/gamm/keeper/math.go @@ -10,6 +10,12 @@ import ( // TODO: Analyze choice here var powPrecision, _ = sdk.NewDecFromStr("0.00000001") +// Singletons +var zero sdk.Dec = sdk.ZeroDec() +var one_half sdk.Dec = sdk.MustNewDecFromStr("0.5") +var one sdk.Dec = sdk.OneDec() +var two sdk.Dec = sdk.MustNewDecFromStr("2") + // calcSpotPrice returns the spot price of the pool // This is the weight-adjusted balance of the tokens in the pool. // so spot_price = (B_in / W_in) / (B_out / W_out) @@ -77,7 +83,7 @@ func calcInGivenOut( diff := tokenBalanceOut.Sub(tokenAmountOut) y := tokenBalanceOut.Quo(diff) foo := pow(y, weightRatio) - foo = foo.Sub(sdk.OneDec()) + foo = foo.Sub(one) tokenAmountIn := sdk.OneDec().Sub(swapFee) return (tokenBalanceIn.Mul(foo)).Quo(tokenAmountIn) @@ -191,7 +197,8 @@ func calcPoolInGivenSingleOut( /*********************************************************/ -func subSign(a, b sdk.Dec) (sdk.Dec, bool) { +// absDifferenceWithSign returns | a - b |, (a - b).sign() +func absDifferenceWithSign(a, b sdk.Dec) (sdk.Dec, bool) { if a.GTE(b) { return a.Sub(b), false } else { @@ -213,7 +220,7 @@ func pow(base sdk.Dec, exp sdk.Dec) sdk.Dec { } // TODO: Remove this if we want to generalize the function, // we can adjust the algorithm in this setting. - if base.GTE(sdk.OneDec().MulInt64(2)) { + if base.GTE(two) { panic(fmt.Errorf("base must be lesser than two")) } @@ -229,13 +236,11 @@ func pow(base sdk.Dec, exp sdk.Dec) sdk.Dec { return integerPow } - partialResult := powApprox(base, fractional, powPrecision) + fractionalPow := powApprox(base, fractional, powPrecision) - return integerPow.Mul(partialResult) + return integerPow.Mul(fractionalPow) } -var one_half sdk.Dec = sdk.MustNewDecFromStr("0.5") - // Contract: 0 < base < 2 func powApprox(base sdk.Dec, exp sdk.Dec, precision sdk.Dec) sdk.Dec { if exp.IsZero() { @@ -254,14 +259,15 @@ func powApprox(base sdk.Dec, exp sdk.Dec, precision sdk.Dec) sdk.Dec { // TODO: Make an approx-equal function, and then check if exp * 3 = 1, and do a check accordingly a := exp - x, xneg := subSign(base, sdk.OneDec()) + x, xneg := absDifferenceWithSign(base, one) term := sdk.OneDec() sum := sdk.OneDec() negative := false + // TODO: Document this computation via taylor expansion for i := 1; term.GTE(precision); i++ { bigK := sdk.OneDec().MulInt64(int64(i)) - c, cneg := subSign(a, bigK.Sub(sdk.OneDec())) + c, cneg := absDifferenceWithSign(a, bigK.Sub(one)) term = term.Mul(c.Mul(x)) term = term.Quo(bigK) diff --git a/x/gamm/keeper/math_test.go b/x/gamm/keeper/math_test.go index 1356aa9433c..2edf8efca7a 100644 --- a/x/gamm/keeper/math_test.go +++ b/x/gamm/keeper/math_test.go @@ -8,13 +8,13 @@ import ( "github.com/stretchr/testify/require" ) -func TestSubSign(t *testing.T) { +func TestAbsDifferenceWithSign(t *testing.T) { decA, err := sdk.NewDecFromStr("3.2") require.NoError(t, err) decB, err := sdk.NewDecFromStr("4.3432389") require.NoError(t, err) - s, b := subSign(decA, decB) + s, b := absDifferenceWithSign(decA, decB) require.True(t, b) expectedDec, err := sdk.NewDecFromStr("1.1432389")