Skip to content

Commit

Permalink
Merge pull request #64 from anatoly-kussul/optimizations-2
Browse files Browse the repository at this point in the history
Optimizations 2
  • Loading branch information
lithammer authored Jan 17, 2025
2 parents 9e9e14d + f34bfba commit fe7fbe7
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 34 deletions.
147 changes: 114 additions & 33 deletions encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,6 @@ type encoder struct {
alphabet alphabet
}

const (
defaultBase = 57
defaultEncLen = 22
defaultNDigits = 10
defaultDivisor = 362033331456891249 // 57^10
)

func maxPow(b uint64) (d uint64, n int) {
d, n = b, 1
for m := math.MaxUint64 / b; d <= m; {
Expand All @@ -39,31 +32,6 @@ func (e encoder) Encode(u uuid.UUID) string {
binary.BigEndian.Uint64(u[8:]),
binary.BigEndian.Uint64(u[:8]),
}
if e.alphabet.len == defaultBase && e.alphabet.maxBytes == 1 {
return e.defaultEncode(num)
}
return e.encode(num)
}

func (e encoder) defaultEncode(num uint128) string { // compiler optimizes a lot of divisions by constant
var i int
var r uint64
var buf [defaultEncLen]byte
for i = defaultEncLen - 1; num.Hi > 0 || num.Lo > 0; {
num, r = num.quoRem64(defaultDivisor)
for j := 0; j < defaultNDigits && i >= 0; j++ {
buf[i] = byte(e.alphabet.chars[r%defaultBase])
r /= defaultBase
i--
}
}
for ; i >= 0; i-- {
buf[i] = byte(e.alphabet.chars[0])
}
return string(buf[:])
}

func (e encoder) encode(num uint128) string {
var r, ind uint64
i := int(e.alphabet.encLen - 1)
buf := make([]byte, int64(e.alphabet.encLen)*int64(e.alphabet.maxBytes))
Expand All @@ -76,7 +44,12 @@ func (e encoder) encode(num uint128) string {
for j := 0; j < n && i >= 0; j++ {
r, ind = r/l, r%l
c := e.alphabet.chars[ind]
lastPlaced -= utf8.EncodeRune(buf[lastPlaced-utf8.RuneLen(c):], c)
if e.alphabet.maxBytes == 1 {
buf[i] = byte(c)
lastPlaced--
} else {
lastPlaced -= utf8.EncodeRune(buf[lastPlaced-utf8.RuneLen(c):], c)
}
i--
}
}
Expand Down Expand Up @@ -109,6 +82,79 @@ func (e encoder) Decode(s string) (u uuid.UUID, err error) {
return
}

const (
b57MaxU64Digits = 10
b57MaxU64Divisor = 362033331456891249 // 57^10
)

type b57Encoder struct{}

func (e b57Encoder) Encode(u uuid.UUID) string {
num := uint128{
binary.BigEndian.Uint64(u[8:]),
binary.BigEndian.Uint64(u[:8]),
}
var r uint64
var buf [22]byte
num, r = num.quoRem64(b57MaxU64Divisor)
buf[21], r = DefaultAlphabet[r%57], r/57
buf[20], r = DefaultAlphabet[r%57], r/57
buf[19], r = DefaultAlphabet[r%57], r/57
buf[18], r = DefaultAlphabet[r%57], r/57
buf[17], r = DefaultAlphabet[r%57], r/57
buf[16], r = DefaultAlphabet[r%57], r/57
buf[15], r = DefaultAlphabet[r%57], r/57
buf[14], r = DefaultAlphabet[r%57], r/57
buf[13] = DefaultAlphabet[r%57]
buf[12] = DefaultAlphabet[r/57]
num, r = num.quoRem64(b57MaxU64Divisor)
buf[11], r = DefaultAlphabet[r%57], r/57
buf[10], r = DefaultAlphabet[r%57], r/57
buf[9], r = DefaultAlphabet[r%57], r/57
buf[8], r = DefaultAlphabet[r%57], r/57
buf[7], r = DefaultAlphabet[r%57], r/57
buf[6], r = DefaultAlphabet[r%57], r/57
buf[5], r = DefaultAlphabet[r%57], r/57
buf[4], r = DefaultAlphabet[r%57], r/57
buf[3] = DefaultAlphabet[r%57]
buf[2] = DefaultAlphabet[r/57]
buf[1] = DefaultAlphabet[num.Lo%57]
buf[0] = DefaultAlphabet[num.Lo/57]
return unsafe.String(unsafe.SliceData(buf[:]), 22)
}

func (e b57Encoder) Decode(s string) (u uuid.UUID, err error) {
var n uint128
var n64, ind, i uint64

for _, c := range s {
if c > 255 {
return u, fmt.Errorf("element '%v' is not part of the alphabet", c)
}
ind = uint64(reverseB57[c])
if ind == 255 {
return u, fmt.Errorf("element '%v' is not part of the alphabet", c)
}
n64 = n64*57 + ind
i++
if i == b57MaxU64Digits {
n, err = n.mulAdd64(b57MaxU64Divisor, n64)
if err != nil {
return
}
i = 0
n64 = 0
}
}
n, err = n.mulAdd64(57*57, n64)
if err != nil {
return
}
binary.BigEndian.PutUint64(u[:8], n.Hi)
binary.BigEndian.PutUint64(u[8:], n.Lo)
return
}

type uint128 struct {
Lo, Hi uint64
}
Expand All @@ -129,3 +175,38 @@ func (u uint128) mulAdd64(m uint64, a uint64) (uint128, error) {
}
return uint128{lo, hi}, nil
}

var reverseB57 = [256]byte{
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 0, 1, 2, 3, 4, 5,
6, 7, 255, 255, 255, 255, 255, 255,
255, 8, 9, 10, 11, 12, 13, 14,
15, 255, 16, 17, 18, 19, 20, 255,
21, 22, 23, 24, 25, 26, 27, 28,
29, 30, 31, 255, 255, 255, 255, 255,
255, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 255, 43, 44, 45,
46, 47, 48, 49, 50, 51, 52, 53,
54, 55, 56, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255,
}
2 changes: 1 addition & 1 deletion shortuuid.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

// DefaultEncoder is the default encoder uses when generating new UUIDs, and is
// based on Base57.
var DefaultEncoder = &encoder{newAlphabet(DefaultAlphabet)}
var DefaultEncoder = b57Encoder{}

// Encoder is an interface for encoding/decoding UUIDs to strings.
type Encoder interface {
Expand Down

0 comments on commit fe7fbe7

Please sign in to comment.