Skip to content

Commit 7363de4

Browse files
committed
Adding interning utility used in khi file format v6
1 parent 61ea8a5 commit 7363de4

2 files changed

Lines changed: 467 additions & 0 deletions

File tree

pkg/model/khifile/v6/intern.go

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
package khifilev6
2+
3+
import (
4+
"iter"
5+
"sort"
6+
"sync"
7+
"unsafe"
8+
9+
pb "github.com/GoogleCloudPlatform/khi/pkg/generated/khifile/v6"
10+
)
11+
12+
// InternStringRef represents a reference to an interned string.
13+
// This struct holds a reference to the pool and the ID of the string.
14+
type InternStringRef struct {
15+
pool *InternPool
16+
id uint32
17+
}
18+
19+
// Resolve returns the original string value.
20+
// It delegates to the pool to resolve the string from the stored ID.
21+
func (r *InternStringRef) Resolve() string {
22+
return r.pool.resolveStringFromID(r.id)
23+
}
24+
25+
// ToProto converts InternStringRef to its proto representation.
26+
func (r *InternStringRef) ToProto() *pb.InternString {
27+
id := r.id
28+
val := r.Resolve()
29+
return &pb.InternString{
30+
Id: &id,
31+
Value: &val,
32+
}
33+
}
34+
35+
// FieldPathSetRef represents a reference to an interned field path set.
36+
// This struct holds a reference to the pool and the ID of the field path set.
37+
type FieldPathSetRef struct {
38+
pool *InternPool
39+
id uint32
40+
}
41+
42+
// Resolve returns the original list of strings in the set.
43+
// It delegates to the pool to resolve the field path set and then resolves each string ID.
44+
func (r *FieldPathSetRef) Resolve() []string {
45+
ids := r.pool.resolveFieldSetFromID(r.id)
46+
res := make([]string, len(ids))
47+
for i, id := range ids {
48+
res[i] = r.pool.resolveStringFromID(id)
49+
}
50+
return res
51+
}
52+
53+
// ToProto converts FieldPathSetRef to its proto representation.
54+
func (r *FieldPathSetRef) ToProto() *pb.InternFieldPathSet {
55+
id := r.id
56+
names := r.pool.resolveFieldSetFromID(r.id)
57+
return &pb.InternFieldPathSet{
58+
Id: &id,
59+
FieldNames: names,
60+
}
61+
}
62+
63+
// InternPool manages interning of strings and field path sets to reduce memory usage.
64+
// It uses sync.Map for concurrent access and relies on IDGenerator for generating IDs.
65+
type InternPool struct {
66+
idGen *IDGenerator
67+
strToID sync.Map // map[string]uint32
68+
idToStr sync.Map // map[uint32]string
69+
70+
fieldSetToID sync.Map // map[string]uint32 (key is byte representation of []uint32)
71+
idToFieldSet sync.Map // map[uint32][]uint32
72+
}
73+
74+
// NewInternPool creates a new InternPool with the given IDGenerator.
75+
func NewInternPool(idGen *IDGenerator) *InternPool {
76+
return &InternPool{
77+
idGen: idGen,
78+
}
79+
}
80+
81+
// InternString returns a InternStringRef for the given string.
82+
// If the string is not already interned, it assigns a new ID from IDGenerator and stores it.
83+
func (p *InternPool) InternString(value string) *InternStringRef {
84+
if id, ok := p.strToID.Load(value); ok {
85+
return &InternStringRef{pool: p, id: id.(uint32)}
86+
}
87+
88+
id := p.idGen.New(IDString)
89+
p.idToStr.Store(id, value)
90+
91+
actual, loaded := p.strToID.LoadOrStore(value, id)
92+
if loaded {
93+
p.idToStr.Store(id, "")
94+
return &InternStringRef{pool: p, id: actual.(uint32)}
95+
}
96+
97+
return &InternStringRef{pool: p, id: id}
98+
}
99+
100+
// resolveStringFromID returns the string corresponding to the given ID.
101+
// It returns an empty string if the ID is not found.
102+
func (p *InternPool) resolveStringFromID(id uint32) string {
103+
if value, ok := p.idToStr.Load(id); ok {
104+
return value.(string)
105+
}
106+
return ""
107+
}
108+
109+
// InternFieldSet returns a FieldPathSetRef for the given list of strings.
110+
// It first interns each string to get its ID, and then interns the resulting list of IDs.
111+
// It uses unsafe string cast for fast lookup in fieldSetToID map without allocation.
112+
func (p *InternPool) InternFieldSet(fieldNames []string) *FieldPathSetRef {
113+
ids := make([]uint32, len(fieldNames))
114+
for i, name := range fieldNames {
115+
ids[i] = p.InternString(name).id
116+
}
117+
118+
// Zero-allocation lookup using unsafe string.
119+
keyLookup := fieldSetKey(ids)
120+
if id, ok := p.fieldSetToID.Load(keyLookup); ok {
121+
return &FieldPathSetRef{pool: p, id: id.(uint32)}
122+
}
123+
124+
id := p.idGen.New(IDFieldSet)
125+
126+
namesCopy := make([]uint32, len(ids))
127+
copy(namesCopy, ids)
128+
p.idToFieldSet.Store(id, namesCopy)
129+
keyStore := fieldSetKey(namesCopy)
130+
131+
actual, loaded := p.fieldSetToID.LoadOrStore(keyStore, id)
132+
if loaded {
133+
p.idToFieldSet.Store(id, []uint32{})
134+
return &FieldPathSetRef{pool: p, id: actual.(uint32)}
135+
}
136+
137+
return &FieldPathSetRef{pool: p, id: id}
138+
}
139+
140+
// resolveFieldSetFromID returns the field path set corresponding to the given ID.
141+
// It returns nil if the ID is not found.
142+
func (p *InternPool) resolveFieldSetFromID(id uint32) []uint32 {
143+
if value, ok := p.idToFieldSet.Load(id); ok {
144+
return value.([]uint32)
145+
}
146+
return nil
147+
}
148+
149+
// SortedStringRefs returns an iterator that yields InternStringRefs in the pool, sorted by their original string value.
150+
func (p *InternPool) SortedStringRefs() iter.Seq[*InternStringRef] {
151+
type entry struct {
152+
val string
153+
id uint32
154+
}
155+
var entries []entry
156+
157+
p.strToID.Range(func(key, value any) bool {
158+
entries = append(entries, entry{
159+
val: key.(string),
160+
id: value.(uint32),
161+
})
162+
return true
163+
})
164+
165+
sort.Slice(entries, func(i, j int) bool {
166+
return entries[i].val < entries[j].val
167+
})
168+
169+
return func(yield func(*InternStringRef) bool) {
170+
for _, e := range entries {
171+
if !yield(&InternStringRef{pool: p, id: e.id}) {
172+
return
173+
}
174+
}
175+
}
176+
}
177+
178+
// FieldSetRefs returns an iterator that yields FieldPathSetRefs in the pool, sorted by their ID.
179+
func (p *InternPool) FieldSetRefs() iter.Seq[*FieldPathSetRef] {
180+
type entry struct {
181+
id uint32
182+
}
183+
var entries []entry
184+
185+
p.fieldSetToID.Range(func(key, value any) bool {
186+
entries = append(entries, entry{
187+
id: value.(uint32),
188+
})
189+
return true
190+
})
191+
192+
// Sort by ID.
193+
sort.Slice(entries, func(i, j int) bool {
194+
return entries[i].id < entries[j].id
195+
})
196+
197+
return func(yield func(*FieldPathSetRef) bool) {
198+
for _, e := range entries {
199+
if !yield(&FieldPathSetRef{pool: p, id: e.id}) {
200+
return
201+
}
202+
}
203+
}
204+
}
205+
206+
// fieldSetKey casts a slice of uint32 to a string without copying.
207+
// The returned string shares memory with the slice. It is safe to use as a map key
208+
// ONLY if the slice is never modified.
209+
func fieldSetKey(ids []uint32) string {
210+
if len(ids) == 0 {
211+
return ""
212+
}
213+
byteSlice := unsafe.Slice((*byte)(unsafe.Pointer(&ids[0])), len(ids)*4)
214+
return unsafe.String(&byteSlice[0], len(byteSlice))
215+
}

0 commit comments

Comments
 (0)