Skip to content

Commit 408a250

Browse files
authored
Remove unnecessary decimal rescaling - memory usage optimization (#160)
* Remove unnecessary decimal rescaling * Move benchmarks to separate file, add new benchmark tests
1 parent 96defcb commit 408a250

3 files changed

Lines changed: 214 additions & 143 deletions

File tree

decimal.go

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,14 @@ func NewFromFloatWithExponent(value float64, exp int32) Decimal {
390390
//
391391
func (d Decimal) rescale(exp int32) Decimal {
392392
d.ensureInitialized()
393+
394+
if d.exp == exp {
395+
return Decimal{
396+
new(big.Int).Set(d.value),
397+
d.exp,
398+
}
399+
}
400+
393401
// NOTE(vadim): must convert exps to float64 before - to prevent overflow
394402
diff := math.Abs(float64(exp) - float64(d.exp))
395403
value := new(big.Int).Set(d.value)
@@ -419,27 +427,23 @@ func (d Decimal) Abs() Decimal {
419427

420428
// Add returns d + d2.
421429
func (d Decimal) Add(d2 Decimal) Decimal {
422-
baseScale := min(d.exp, d2.exp)
423-
rd := d.rescale(baseScale)
424-
rd2 := d2.rescale(baseScale)
430+
rd, rd2 := RescalePair(d, d2)
425431

426432
d3Value := new(big.Int).Add(rd.value, rd2.value)
427433
return Decimal{
428434
value: d3Value,
429-
exp: baseScale,
435+
exp: rd.exp,
430436
}
431437
}
432438

433439
// Sub returns d - d2.
434440
func (d Decimal) Sub(d2 Decimal) Decimal {
435-
baseScale := min(d.exp, d2.exp)
436-
rd := d.rescale(baseScale)
437-
rd2 := d2.rescale(baseScale)
441+
rd, rd2 := RescalePair(d, d2)
438442

439443
d3Value := new(big.Int).Sub(rd.value, rd2.value)
440444
return Decimal{
441445
value: d3Value,
442-
exp: baseScale,
446+
exp: rd.exp,
443447
}
444448
}
445449

@@ -600,9 +604,7 @@ func (d Decimal) Cmp(d2 Decimal) int {
600604
return d.value.Cmp(d2.value)
601605
}
602606

603-
baseExp := min(d.exp, d2.exp)
604-
rd := d.rescale(baseExp)
605-
rd2 := d2.rescale(baseExp)
607+
rd, rd2 := RescalePair(d, d2)
606608

607609
return rd.value.Cmp(rd2.value)
608610
}
@@ -1172,6 +1174,22 @@ func Avg(first Decimal, rest ...Decimal) Decimal {
11721174
return sum.Div(count)
11731175
}
11741176

1177+
// Rescale two decimals to common exponential value (minimal exp of both decimals)
1178+
func RescalePair(d1 Decimal, d2 Decimal) (Decimal, Decimal) {
1179+
d1.ensureInitialized()
1180+
d2.ensureInitialized()
1181+
1182+
if d1.exp == d2.exp {
1183+
return d1, d2
1184+
}
1185+
1186+
baseScale := min(d1.exp, d2.exp)
1187+
if baseScale != d1.exp {
1188+
return d1.rescale(baseScale), d2
1189+
}
1190+
return d1, d2.rescale(baseScale)
1191+
}
1192+
11751193
func min(x, y int32) int32 {
11761194
if x >= y {
11771195
return y

decimal_bench_test.go

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package decimal
2+
3+
import (
4+
"math"
5+
"math/rand"
6+
"sort"
7+
"strconv"
8+
"testing"
9+
)
10+
11+
type DecimalSlice []Decimal
12+
13+
func (p DecimalSlice) Len() int { return len(p) }
14+
func (p DecimalSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
15+
func (p DecimalSlice) Less(i, j int) bool { return p[i].Cmp(p[j]) < 0 }
16+
17+
func BenchmarkNewFromFloatWithExponent(b *testing.B) {
18+
rng := rand.New(rand.NewSource(0xdead1337))
19+
in := make([]float64, b.N)
20+
for i := range in {
21+
in[i] = rng.NormFloat64() * 10e20
22+
}
23+
b.ReportAllocs()
24+
b.StartTimer()
25+
for i := 0; i < b.N; i++ {
26+
in := rng.NormFloat64() * 10e20
27+
_ = NewFromFloatWithExponent(in, math.MinInt32)
28+
}
29+
}
30+
31+
func BenchmarkNewFromFloat(b *testing.B) {
32+
rng := rand.New(rand.NewSource(0xdead1337))
33+
in := make([]float64, b.N)
34+
for i := range in {
35+
in[i] = rng.NormFloat64() * 10e20
36+
}
37+
b.ReportAllocs()
38+
b.StartTimer()
39+
for i := 0; i < b.N; i++ {
40+
_ = NewFromFloat(in[i])
41+
}
42+
}
43+
44+
func BenchmarkNewFromStringFloat(b *testing.B) {
45+
rng := rand.New(rand.NewSource(0xdead1337))
46+
in := make([]float64, b.N)
47+
for i := range in {
48+
in[i] = rng.NormFloat64() * 10e20
49+
}
50+
b.ReportAllocs()
51+
b.StartTimer()
52+
for i := 0; i < b.N; i++ {
53+
in := strconv.FormatFloat(in[i], 'f', -1, 64)
54+
_, _ = NewFromString(in)
55+
}
56+
}
57+
58+
func Benchmark_FloorFast(b *testing.B) {
59+
input := New(200, 2)
60+
b.ResetTimer()
61+
for i := 0; i < b.N; i++ {
62+
input.Floor()
63+
}
64+
}
65+
66+
func Benchmark_FloorRegular(b *testing.B) {
67+
input := New(200, -2)
68+
b.ResetTimer()
69+
for i := 0; i < b.N; i++ {
70+
input.Floor()
71+
}
72+
}
73+
74+
func Benchmark_DivideOriginal(b *testing.B) {
75+
tcs := createDivTestCases()
76+
b.ResetTimer()
77+
for i := 0; i < b.N; i++ {
78+
for _, tc := range tcs {
79+
d := tc.d
80+
if sign(tc.d2) == 0 {
81+
continue
82+
}
83+
d2 := tc.d2
84+
prec := tc.prec
85+
a := d.DivOld(d2, int(prec))
86+
if sign(a) > 2 {
87+
panic("dummy panic")
88+
}
89+
}
90+
}
91+
}
92+
93+
func Benchmark_DivideNew(b *testing.B) {
94+
tcs := createDivTestCases()
95+
b.ResetTimer()
96+
for i := 0; i < b.N; i++ {
97+
for _, tc := range tcs {
98+
d := tc.d
99+
if sign(tc.d2) == 0 {
100+
continue
101+
}
102+
d2 := tc.d2
103+
prec := tc.prec
104+
a := d.DivRound(d2, prec)
105+
if sign(a) > 2 {
106+
panic("dummy panic")
107+
}
108+
}
109+
}
110+
}
111+
112+
func BenchmarkDecimal_RoundCash_Five(b *testing.B) {
113+
const want = "3.50"
114+
for i := 0; i < b.N; i++ {
115+
val := New(3478, -3)
116+
if have := val.StringFixedCash(5); have != want {
117+
b.Fatalf("\nHave: %q\nWant: %q", have, want)
118+
}
119+
}
120+
}
121+
122+
func BenchmarkDecimal_RoundCash_Fifteen(b *testing.B) {
123+
const want = "6.30"
124+
for i := 0; i < b.N; i++ {
125+
val := New(635, -2)
126+
if have := val.StringFixedCash(15); have != want {
127+
b.Fatalf("\nHave: %q\nWant: %q", have, want)
128+
}
129+
}
130+
}
131+
132+
func Benchmark_Cmp(b *testing.B) {
133+
decimals := DecimalSlice([]Decimal{})
134+
for i := 0; i < 1000000; i++ {
135+
decimals = append(decimals, New(int64(i), 0))
136+
}
137+
b.ResetTimer()
138+
for i := 0; i < b.N; i++ {
139+
sort.Sort(decimals)
140+
}
141+
}
142+
143+
func Benchmark_decimal_Decimal_Add_different_precision(b *testing.B) {
144+
d1 := NewFromFloat(1000.123)
145+
d2 := NewFromFloat(500).Mul(NewFromFloat(0.12))
146+
147+
b.ReportAllocs()
148+
b.StartTimer()
149+
for i := 0; i < b.N; i++ {
150+
d1.Add(d2)
151+
}
152+
}
153+
154+
func Benchmark_decimal_Decimal_Sub_different_precision(b *testing.B) {
155+
d1 := NewFromFloat(1000.123)
156+
d2 := NewFromFloat(500).Mul(NewFromFloat(0.12))
157+
158+
b.ReportAllocs()
159+
b.StartTimer()
160+
for i := 0; i < b.N; i++ {
161+
d1.Sub(d2)
162+
}
163+
}
164+
165+
func Benchmark_decimal_Decimal_Add_same_precision(b *testing.B) {
166+
d1 := NewFromFloat(1000.123)
167+
d2 := NewFromFloat(500.123)
168+
169+
b.ReportAllocs()
170+
b.StartTimer()
171+
for i := 0; i < b.N; i++ {
172+
d1.Add(d2)
173+
}
174+
}
175+
176+
func Benchmark_decimal_Decimal_Sub_same_precision(b *testing.B) {
177+
d1 := NewFromFloat(1000.123)
178+
d2 := NewFromFloat(500.123)
179+
180+
b.ReportAllocs()
181+
b.StartTimer()
182+
for i := 0; i < b.N; i++ {
183+
d1.Add(d2)
184+
}
185+
}

0 commit comments

Comments
 (0)