Skip to content

Commit

Permalink
apd: Improve string conversion efficiency
Browse files Browse the repository at this point in the history
Prefer to use Append and allocate scratch space
to improve speed and efficiency of string conversion:

```
name              old time/op    new time/op    delta
DecimalString-10     147ns ± 1%      56ns ± 4%  -62.14%  (p=0.000
n=10+10)

name              old alloc/op   new alloc/op   delta
DecimalString-10     84.0B ± 0%     39.0B ± 0%  -53.57%  (p=0.000
n=10+10)

name              old allocs/op  new allocs/op  delta
DecimalString-10      5.00 ± 0%      1.00 ± 0%  -80.00%  (p=0.000
n=10+10)
```
  • Loading branch information
Yevgeniy Miretskiy committed Oct 17, 2022
1 parent e2030eb commit 9d47f6a
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 10 deletions.
29 changes: 24 additions & 5 deletions bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ import (
// runBenches benchmarks a given function on random decimals on combinations of
// three parameters:
//
// precision: desired output precision
// inScale: the scale of the input decimal: the absolute value will be between
// 10^inScale and 10^(inScale+1)
// inNumDigits: number of digits in the input decimal; if negative the number
// will be negative and the number of digits are the absolute value.
// precision: desired output precision
// inScale: the scale of the input decimal: the absolute value will be between
// 10^inScale and 10^(inScale+1)
// inNumDigits: number of digits in the input decimal; if negative the number
// will be negative and the number of digits are the absolute value.
func runBenches(
b *testing.B, precision, inScale, inNumDigits []int, fn func(*testing.B, *Context, *Decimal),
) {
Expand Down Expand Up @@ -103,3 +103,22 @@ func BenchmarkLn(b *testing.B) {
},
)
}

func BenchmarkDecimalString(b *testing.B) {
rng := rand.New(rand.NewSource(461210934723948))
corpus := func() []Decimal {
res := make([]Decimal, 8192)
for i := range res {
_, err := res[i].SetFloat64(rng.Float64())
if err != nil {
b.Fatal(err)
}
}
return res
}()
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = corpus[rng.Intn(len(corpus))].String()
}
}
11 changes: 6 additions & 5 deletions format.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import (
// zeros (the 'g' format avoids the use of 'f' in this case). All other
// formats always show the exact precision of the Decimal.
func (d *Decimal) Text(format byte) string {
cap := 10 // TODO(gri) determine a good/better value here
return string(d.Append(make([]byte, 0, cap), format))
var buf [16]byte
return string(d.Append(buf[:0], format))
}

// String formats x like x.Text('G'). It matches the to-scientific-string
Expand Down Expand Up @@ -57,7 +57,8 @@ func (d *Decimal) Append(buf []byte, fmt byte) []byte {
return append(buf, "unknown"...)
}

digits := d.Coeff.String()
var scratch [16]byte
digits := d.Coeff.Append(scratch[:0], 10)
switch fmt {
case 'e', 'E':
return fmtE(buf, fmt, d, digits)
Expand All @@ -83,7 +84,7 @@ func (d *Decimal) Append(buf []byte, fmt byte) []byte {
}

// %e: d.ddddde±d
func fmtE(buf []byte, fmt byte, d *Decimal, digits string) []byte {
func fmtE(buf []byte, fmt byte, d *Decimal, digits []byte) []byte {
adj := int64(d.Exponent) + int64(len(digits)) - 1
buf = append(buf, digits[0])
if len(digits) > 1 {
Expand All @@ -103,7 +104,7 @@ func fmtE(buf []byte, fmt byte, d *Decimal, digits string) []byte {
}

// %f: ddddddd.ddddd
func fmtF(buf []byte, d *Decimal, digits string) []byte {
func fmtF(buf []byte, d *Decimal, digits []byte) []byte {
if d.Exponent < 0 {
if left := -int(d.Exponent) - len(digits); left >= 0 {
buf = append(buf, "0."...)
Expand Down

0 comments on commit 9d47f6a

Please sign in to comment.