Skip to content

Commit 524ca6a

Browse files
authored
reduce allocations from guids (#692)
* reduce allocations from guids * fix * fix * less lock thrashing
1 parent e418881 commit 524ca6a

File tree

2 files changed

+104
-13
lines changed

2 files changed

+104
-13
lines changed

utils/id.go

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,19 @@
1515
package utils
1616

1717
import (
18+
"crypto/rand"
1819
"crypto/sha1"
20+
"encoding/binary"
1921
"fmt"
2022
"os"
23+
"sync"
24+
"time"
2125

2226
"github.com/jxskiss/base62"
2327
"github.com/lithammer/shortuuid/v4"
2428

2529
"github.com/livekit/protocol/livekit"
30+
"github.com/livekit/protocol/logger"
2631
)
2732

2833
const GuidSize = 12
@@ -45,8 +50,10 @@ const (
4550
AgentJobPrefix = "AJ_"
4651
)
4752

53+
var defaultGuidGenerator = newGuidGenerator(4096, GuidSize+10)
54+
4855
func NewGuid(prefix string) string {
49-
return prefix + shortuuid.New()[:GuidSize]
56+
return defaultGuidGenerator.NewGuid(prefix)
5057
}
5158

5259
// HashedID creates a hashed ID from a unique string
@@ -66,17 +73,91 @@ func LocalNodeID() (string, error) {
6673
return fmt.Sprintf("%s%s", NodePrefix, HashedID(hostname)[:8]), nil
6774
}
6875

69-
var b62Index = newB62Index()
70-
var b62Chars = []byte(shortuuid.DefaultAlphabet)
76+
var b57Index = newB57Index()
77+
var b57Chars = []byte(shortuuid.DefaultAlphabet)
7178

72-
func newB62Index() [256]byte {
79+
func newB57Index() [256]byte {
7380
var index [256]byte
74-
for i := 0; i < len(b62Chars); i++ {
75-
index[b62Chars[i]] = byte(i)
81+
for i := 0; i < len(b57Chars); i++ {
82+
index[b57Chars[i]] = byte(i)
7683
}
7784
return index
7885
}
7986

87+
type guidGenerator struct {
88+
pool sync.Pool
89+
mu sync.Mutex
90+
buf []byte
91+
pos int
92+
}
93+
94+
func newGuidGenerator(bufSize, scratchSize int) *guidGenerator {
95+
return &guidGenerator{
96+
pool: sync.Pool{
97+
New: func() any {
98+
b := make([]byte, scratchSize)
99+
return &b
100+
},
101+
},
102+
buf: make([]byte, bufSize),
103+
pos: bufSize,
104+
}
105+
}
106+
107+
func (g *guidGenerator) refillBuf() {
108+
for {
109+
_, err := rand.Read(g.buf)
110+
if err == nil {
111+
return
112+
}
113+
114+
logger.Errorw("unable to refill guid buffer", err)
115+
time.Sleep(time.Millisecond)
116+
}
117+
}
118+
119+
func (g *guidGenerator) uint32() uint32 {
120+
if g.pos == len(g.buf) {
121+
g.refillBuf()
122+
g.pos = 0
123+
}
124+
125+
n := binary.BigEndian.Uint32(g.buf[g.pos:])
126+
g.pos += 4
127+
128+
return n
129+
}
130+
131+
func (g *guidGenerator) readIDChars(b []byte) {
132+
g.mu.Lock()
133+
defer g.mu.Unlock()
134+
135+
var n int
136+
for {
137+
r := g.uint32()
138+
for i := 0; i < 5; i++ {
139+
if int(r&0x3f) < len(b57Chars) {
140+
b[n] = b57Chars[r&0x3f]
141+
n++
142+
if n == len(b) {
143+
return
144+
}
145+
}
146+
r >>= 6
147+
}
148+
}
149+
}
150+
151+
func (g *guidGenerator) NewGuid(prefix string) string {
152+
b := g.pool.Get().(*[]byte)
153+
defer g.pool.Put(b)
154+
155+
*b = append((*b)[:0], make([]byte, len(prefix)+GuidSize)...)
156+
copy(*b, prefix)
157+
g.readIDChars((*b)[len(prefix):])
158+
return string(*b)
159+
}
160+
80161
func guidPrefix[T livekit.Guid]() string {
81162
var id T
82163
switch any(id).(type) {
@@ -97,9 +178,9 @@ func MarshalGuid[T livekit.Guid](id T) livekit.GuidBlock {
97178
for i := 0; i < 3; i++ {
98179
j := i * 3
99180
k := i * 4
100-
b[j] = b62Index[idb[k]]<<2 | b62Index[idb[k+1]]>>4
101-
b[j+1] = b62Index[idb[k+1]]<<4 | b62Index[idb[k+2]]>>2
102-
b[j+2] = b62Index[idb[k+2]]<<6 | b62Index[idb[k+3]]
181+
b[j] = b57Index[idb[k]]<<2 | b57Index[idb[k+1]]>>4
182+
b[j+1] = b57Index[idb[k+1]]<<4 | b57Index[idb[k+2]]>>2
183+
b[j+2] = b57Index[idb[k+2]]<<6 | b57Index[idb[k+3]]
103184
}
104185
return b
105186
}
@@ -112,10 +193,10 @@ func UnmarshalGuid[T livekit.Guid](b livekit.GuidBlock) T {
112193
for i := 0; i < 3; i++ {
113194
j := i * 3
114195
k := i * 4
115-
idb[k] = b62Chars[b[j]>>2]
116-
idb[k+1] = b62Chars[(b[j]&3)<<4|b[j+1]>>4]
117-
idb[k+2] = b62Chars[(b[j+1]&15)<<2|b[j+2]>>6]
118-
idb[k+3] = b62Chars[b[j+2]&63]
196+
idb[k] = b57Chars[b[j]>>2]
197+
idb[k+1] = b57Chars[(b[j]&3)<<4|b[j+1]>>4]
198+
idb[k+2] = b57Chars[(b[j+1]&15)<<2|b[j+2]>>6]
199+
idb[k+3] = b57Chars[b[j+2]&63]
119200
}
120201
return T(id)
121202
}

utils/id_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,13 @@ func TestMarshalUnmarshalGuid(t *testing.T) {
3030
require.EqualValues(t, id0, id1)
3131
require.EqualValues(t, b0, b1)
3232
}
33+
34+
func BenchmarkNewGuid(b *testing.B) {
35+
b.Run("new", func(b *testing.B) {
36+
var guid string
37+
for i := 0; i < b.N; i++ {
38+
guid = NewGuid(TrackPrefix)
39+
}
40+
_ = guid
41+
})
42+
}

0 commit comments

Comments
 (0)