lbcutil/base58/base58.go
Josh Bleecher Snyder a53e38424c base58: allocate less in Encode
* calculate maximum output length more precisely
  to avoid allocation in the append
* use big.Int.Sign instead of needing bigZero

name                 old time/op    new time/op    delta
Base58Encode_5K-8      5.86ms ± 3%    5.79ms ± 2%   -1.27%  (p=0.035 n=9+10)
Base58Encode_100K-8     2.23s ± 1%     2.23s ± 0%     ~     (p=0.074 n=9+8)
Base58Decode_5K-8       281µs ± 1%     282µs ± 1%     ~     (p=0.720 n=9+10)
Base58Decode_100K-8    89.4ms ± 7%    88.3ms ± 7%     ~     (p=0.123 n=10+10)

name                 old speed      new speed      delta
Base58Encode_5K-8     854kB/s ± 3%   864kB/s ± 2%     ~     (p=0.134 n=9+10)
Base58Encode_100K-8  40.0kB/s ± 0%  40.0kB/s ± 0%     ~     (all equal)
Base58Decode_5K-8    24.3MB/s ± 1%  24.2MB/s ± 1%     ~     (p=0.644 n=9+10)
Base58Decode_100K-8  1.53MB/s ± 7%  1.55MB/s ± 7%     ~     (p=0.218 n=10+10)

name                 old alloc/op   new alloc/op   delta
Base58Encode_5K-8      28.7kB ± 0%    19.2kB ± 0%  -33.03%  (p=0.000 n=10+10)
Base58Encode_100K-8     557kB ± 0%     385kB ± 0%  -30.88%  (p=0.000 n=10+10)
Base58Decode_5K-8       349kB ± 0%     349kB ± 0%     ~     (all equal)
Base58Decode_100K-8     133MB ± 0%     133MB ± 0%     ~     (p=0.183 n=10+10)

name                 old allocs/op  new allocs/op  delta
Base58Encode_5K-8        5.00 ± 0%      4.00 ± 0%  -20.00%  (p=0.000 n=10+10)
Base58Encode_100K-8      5.00 ± 0%      4.00 ± 0%  -20.00%  (p=0.000 n=10+10)
Base58Decode_5K-8         129 ± 0%       129 ± 0%     ~     (all equal)
Base58Decode_100K-8     2.51k ± 0%     2.51k ± 0%     ~     (p=0.321 n=10+10)

When Go 1.16 is released, performance will improve
significantly due to improvements to math/big.Int's division implementation.
2020-12-08 09:37:02 -05:00

138 lines
3.2 KiB
Go

// Copyright (c) 2013-2015 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package base58
import (
"math/big"
)
//go:generate go run genalphabet.go
var bigRadix = [...]*big.Int{
big.NewInt(0),
big.NewInt(58),
big.NewInt(58 * 58),
big.NewInt(58 * 58 * 58),
big.NewInt(58 * 58 * 58 * 58),
big.NewInt(58 * 58 * 58 * 58 * 58),
big.NewInt(58 * 58 * 58 * 58 * 58 * 58),
big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58),
big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58),
big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58),
bigRadix10,
}
var bigRadix10 = big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58) // 58^10
// Decode decodes a modified base58 string to a byte slice.
func Decode(b string) []byte {
answer := big.NewInt(0)
scratch := new(big.Int)
// Calculating with big.Int is slow for each iteration.
// x += b58[b[i]] * j
// j *= 58
//
// Instead we can try to do as much calculations on int64.
// We can represent a 10 digit base58 number using an int64.
//
// Hence we'll try to convert 10, base58 digits at a time.
// The rough idea is to calculate `t`, such that:
//
// t := b58[b[i+9]] * 58^9 ... + b58[b[i+1]] * 58^1 + b58[b[i]] * 58^0
// x *= 58^10
// x += t
//
// Of course, in addition, we'll need to handle boundary condition when `b` is not multiple of 58^10.
// In that case we'll use the bigRadix[n] lookup for the appropriate power.
for t := b; len(t) > 0; {
n := len(t)
if n > 10 {
n = 10
}
total := uint64(0)
for _, v := range t[:n] {
tmp := b58[v]
if tmp == 255 {
return []byte("")
}
total = total*58 + uint64(tmp)
}
answer.Mul(answer, bigRadix[n])
scratch.SetUint64(total)
answer.Add(answer, scratch)
t = t[n:]
}
tmpval := answer.Bytes()
var numZeros int
for numZeros = 0; numZeros < len(b); numZeros++ {
if b[numZeros] != alphabetIdx0 {
break
}
}
flen := numZeros + len(tmpval)
val := make([]byte, flen)
copy(val[numZeros:], tmpval)
return val
}
// Encode encodes a byte slice to a modified base58 string.
func Encode(b []byte) string {
x := new(big.Int)
x.SetBytes(b)
// maximum length of output is log58(2^(8*len(b))) == len(b) * 8 / log(58)
maxlen := int(float64(len(b))*1.365658237309761) + 1
answer := make([]byte, 0, maxlen)
mod := new(big.Int)
for x.Sign() > 0 {
// Calculating with big.Int is slow for each iteration.
// x, mod = x / 58, x % 58
//
// Instead we can try to do as much calculations on int64.
// x, mod = x / 58^10, x % 58^10
//
// Which will give us mod, which is 10 digit base58 number.
// We'll loop that 10 times to convert to the answer.
x.DivMod(x, bigRadix10, mod)
if x.Sign() == 0 {
// When x = 0, we need to ensure we don't add any extra zeros.
m := mod.Int64()
for m > 0 {
answer = append(answer, alphabet[m%58])
m /= 58
}
} else {
m := mod.Int64()
for i := 0; i < 10; i++ {
answer = append(answer, alphabet[m%58])
m /= 58
}
}
}
// leading zero bytes
for _, i := range b {
if i != 0 {
break
}
answer = append(answer, alphabetIdx0)
}
// reverse
alen := len(answer)
for i := 0; i < alen/2; i++ {
answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i]
}
return string(answer)
}