base58: optimize Encode

Before:
    BenchmarkBase58Encode_5K-32     46      23934763 ns/op  0.21 MB/s
    BenchmarkBase58Encode_100K-32    1    9351948600 ns/op  0.01 MB/s

After:
    BenchmarkBase58Encode_5K-32    501       2419129 ns/op  2.07 MB/s
    BenchmarkBase58Encode_100K-32    2     923507950 ns/op  0.11 MB/s
This commit is contained in:
Egon Elbre 2020-09-10 17:54:35 +03:00 committed by John C. Vernaleo
parent a21f014935
commit ed1fc7ad99
2 changed files with 52 additions and 17 deletions

View file

@ -11,6 +11,7 @@ import (
//go:generate go run genalphabet.go
var bigRadix = big.NewInt(58)
var bigRadix10 = big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58) // 58^10
var bigZero = big.NewInt(0)
// Decode decodes a modified base58 string to a byte slice.
@ -51,10 +52,32 @@ func Encode(b []byte) string {
x.SetBytes(b)
answer := make([]byte, 0, len(b)*136/100)
mod := new(big.Int)
for x.Cmp(bigZero) > 0 {
mod := new(big.Int)
x.DivMod(x, bigRadix, mod)
answer = append(answer, alphabet[mod.Int64()])
// 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.Cmp(bigZero) == 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

View file

@ -11,25 +11,37 @@ import (
"github.com/btcsuite/btcutil/base58"
)
func BenchmarkBase58Encode(b *testing.B) {
b.StopTimer()
data := bytes.Repeat([]byte{0xff}, 5000)
b.SetBytes(int64(len(data)))
b.StartTimer()
var (
raw5k = bytes.Repeat([]byte{0xff}, 5000)
raw100k = bytes.Repeat([]byte{0xff}, 100*1000)
encoded5k = base58.Encode(raw5k)
encoded100k = base58.Encode(raw100k)
)
func BenchmarkBase58Encode_5K(b *testing.B) {
b.SetBytes(int64(len(raw5k)))
for i := 0; i < b.N; i++ {
base58.Encode(data)
base58.Encode(raw5k)
}
}
func BenchmarkBase58Decode(b *testing.B) {
b.StopTimer()
data := bytes.Repeat([]byte{0xff}, 5000)
encoded := base58.Encode(data)
b.SetBytes(int64(len(encoded)))
b.StartTimer()
func BenchmarkBase58Encode_100K(b *testing.B) {
b.SetBytes(int64(len(raw100k)))
for i := 0; i < b.N; i++ {
base58.Decode(encoded)
base58.Encode(raw100k)
}
}
func BenchmarkBase58Decode_5K(b *testing.B) {
b.SetBytes(int64(len(encoded5k)))
for i := 0; i < b.N; i++ {
base58.Decode(encoded5k)
}
}
func BenchmarkBase58Decode_100K(b *testing.B) {
b.SetBytes(int64(len(encoded100k)))
for i := 0; i < b.N; i++ {
base58.Decode(encoded100k)
}
}