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

View file

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