Skip to content

Commit

Permalink
Reduce String() allocations
Browse files Browse the repository at this point in the history
  • Loading branch information
jgraeger committed Feb 14, 2024
1 parent 13d73d2 commit 14f83db
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 18 deletions.
27 changes: 20 additions & 7 deletions base32/base32.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,17 @@ func EncodeLower(src [16]byte) string {
return Encode(src, alphLow)
}

// Encode encodes the src [16]byte into a base32 string with the given alphabet.
// The alphabet must be 32 bytes long. If the alphabet is shorter, the function
// will panic. It's the callers responsiblity to ensure the alphabet is valid.
//
// Direct usage is discouraged. Use EncodeUpper or EncodeLower instead.
func Encode(src [16]byte, alphabet string) string {
dst := make([]byte, 26)
// EncodeLowerTo encodes the src [16]byte into a provided 26-byte buffer using uppercase letters.
func EncodeUpperTo(dst []byte, src [16]byte) {
EncodeTo(dst, src, alphUp)
}

// EncodeLowerTo encodes the src [16]byte into a provided 26-byte buffer using lowercase letters.
func EncodeLowerTo(dst []byte, src [16]byte) {
EncodeTo(dst, src, alphLow)
}

func EncodeTo(dst []byte, src [16]byte, alphabet string) {
// Optimized unrolled loop ahead.

// 10 byte timestamp
Expand Down Expand Up @@ -72,7 +76,16 @@ func Encode(src [16]byte, alphabet string) string {
dst[23] = alphabet[(src[14]&124)>>2]
dst[24] = alphabet[((src[14]&3)<<3)|((src[15]&224)>>5)]
dst[25] = alphabet[src[15]&31]
}

// Encode encodes the src [16]byte into a base32 string with the given alphabet.
// The alphabet must be 32 bytes long. If the alphabet is shorter, the function
// will panic. It's the callers responsiblity to ensure the alphabet is valid.
//
// Direct usage is discouraged. Use EncodeUpper or EncodeLower instead.
func Encode(src [16]byte, alphabet string) string {
dst := make([]byte, 26)
EncodeTo(dst, src, alphabet)
return string(dst)
}

Expand Down
3 changes: 3 additions & 0 deletions benchmark/benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func BenchmarkFromString(b *testing.B) {
func benchStringRandom(n int) (string, func(*testing.B)) {
ids := makeSortableIDs(n)
return fmt.Sprintf("n=%d", n), func(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for idx := range ids {
Expand All @@ -99,6 +100,7 @@ func benchStringRandom(n int) (string, func(*testing.B)) {
func benchStringSortable(n int) (string, func(*testing.B)) {
ids := makeSortableIDs(n)
return fmt.Sprintf("n=%d", n), func(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for idx := range ids {
Expand All @@ -112,6 +114,7 @@ func benchStringSortable(n int) (string, func(*testing.B)) {
func benchStringJp(n int) (string, func(*testing.B)) {
ids := makeJpTypeIDs(n)
return fmt.Sprintf("n=%d", n), func(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for idx := range ids {
Expand Down
10 changes: 6 additions & 4 deletions benchmark/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ go 1.21.6

toolchain go1.21.7

require github.com/sumup/typeid v0.0.0-20240207125954-757b87eaff3c
replace github.com/sumup/typeid => ../

require (
github.com/sumup/typeid v0.0.0-20240207125954-757b87eaff3c
go.jetpack.io/typeid v1.0.0
)

require (
github.com/gofrs/uuid/v5 v5.0.0 // indirect
github.com/jackc/pgx/v5 v5.5.3 // indirect
go.jetpack.io/typeid v1.0.0 // indirect
golang.org/x/crypto v0.18.0 // indirect
golang.org/x/sync v0.5.0 // indirect
)
8 changes: 2 additions & 6 deletions benchmark/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,12 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/sumup/typeid v0.0.0-20240207125954-757b87eaff3c h1:jHCDCEs+alb1DCheUJxR7IlXKr6Qn7cFUBVoZrxZoyQ=
github.com/sumup/typeid v0.0.0-20240207125954-757b87eaff3c/go.mod h1:gMQ6rvv9//+PumvkKu1tAOyXW/u9Pwg1dkbPWhQ62vk=
go.jetpack.io/typeid v0.1.0 h1:suTmjNR3y2em2gCTG06agFfcACm3+zuxfziMUk5UXnw=
go.jetpack.io/typeid v0.1.0/go.mod h1:E11ObFkKlvsSTxwwYIm+zpbsRthjIMDy/JGBogs2vSo=
go.jetpack.io/typeid v1.0.0 h1:8gQ+iYGdyiQ0Pr40ydSB/PzMOIwlXX5DTojp1CBeSPQ=
go.jetpack.io/typeid v1.0.0/go.mod h1:+UPEaECUgFxgAjFPn5Yf9eO/3ft/3xZ98Eahv9JW/GQ=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
Expand Down
11 changes: 10 additions & 1 deletion generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package typeid

import (
"fmt"
"unsafe"

"github.com/gofrs/uuid/v5"
)
Expand All @@ -11,6 +12,8 @@ import (
type processor struct {
// b32Encode applies a base32 encoding to a UUID.
b32Encode func(uuid.UUID) string
// b32EncodeTo applies a base32 encoding to a UUID and copies the result into a provided 26-byte buffer.
b32EncodeTo func([]byte, uuid.UUID)
// b32Decode decode a UUID using the resp. base32 decoding.
b32Decode func(string) (uuid.UUID, error)
// Generates a new universal unique identifer.
Expand Down Expand Up @@ -89,5 +92,11 @@ func toString[P Prefix](suffix uuid.UUID, p *processor) string {
if prefix == "" {
return p.b32Encode(suffix)
}
return prefix + "_" + p.b32Encode(suffix)

buf := make([]byte, len(prefix)+1+suffixStrLen)
copy(buf, prefix)
copy(buf[len(prefix):], "_")
p.b32EncodeTo(buf[len(prefix)+1:], suffix)

return unsafe.String(unsafe.SliceData(buf), len(buf))
}
3 changes: 3 additions & 0 deletions random.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ var randomIDProc = &processor{
b32Encode: func(u uuid.UUID) string {
return base32.EncodeUpper([16]byte(u))
},
b32EncodeTo: func(dst []byte, u uuid.UUID) {
base32.EncodeUpperTo(dst, [16]byte(u))
},
b32Decode: func(s string) (uuid.UUID, error) {
decoded, err := base32.DecodeUpper(s)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions sortable.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ var sortableIDProc = &processor{
b32Encode: func(u uuid.UUID) string {
return base32.EncodeLower([16]byte(u))
},
b32EncodeTo: func(dst []byte, u uuid.UUID) {
base32.EncodeLowerTo(dst, [16]byte(u))
},
b32Decode: func(s string) (uuid.UUID, error) {
decoded, err := base32.DecodeLower(s)
if err != nil {
Expand Down

0 comments on commit 14f83db

Please sign in to comment.