Skip to content

Commit 9c81a91

Browse files
authored
resolver: Make EndpointMap's Get, Set and Delete operations O(1) (#8179) (#8184)
1 parent 8d46b21 commit 9c81a91

File tree

2 files changed

+65
-60
lines changed

2 files changed

+65
-60
lines changed

resolver/map.go

+43-60
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818

1919
package resolver
2020

21+
import (
22+
"encoding/base64"
23+
"sort"
24+
"strings"
25+
)
26+
2127
type addressMapEntry struct {
2228
addr Address
2329
value any
@@ -137,66 +143,61 @@ func (a *AddressMap) Values() []any {
137143
return ret
138144
}
139145

140-
type endpointNode struct {
141-
addrs map[string]struct{}
142-
}
143-
144-
// Equal returns whether the unordered set of addrs are the same between the
145-
// endpoint nodes.
146-
func (en *endpointNode) Equal(en2 *endpointNode) bool {
147-
if len(en.addrs) != len(en2.addrs) {
148-
return false
149-
}
150-
for addr := range en.addrs {
151-
if _, ok := en2.addrs[addr]; !ok {
152-
return false
153-
}
154-
}
155-
return true
156-
}
157-
158-
func toEndpointNode(endpoint Endpoint) endpointNode {
159-
en := make(map[string]struct{})
160-
for _, addr := range endpoint.Addresses {
161-
en[addr.Addr] = struct{}{}
162-
}
163-
return endpointNode{
164-
addrs: en,
165-
}
166-
}
146+
type endpointMapKey string
167147

168148
// EndpointMap is a map of endpoints to arbitrary values keyed on only the
169149
// unordered set of address strings within an endpoint. This map is not thread
170150
// safe, thus it is unsafe to access concurrently. Must be created via
171151
// NewEndpointMap; do not construct directly.
172152
type EndpointMap struct {
173-
endpoints map[*endpointNode]any
153+
endpoints map[endpointMapKey]endpointData
154+
}
155+
156+
type endpointData struct {
157+
// decodedKey stores the original key to avoid decoding when iterating on
158+
// EndpointMap keys.
159+
decodedKey Endpoint
160+
value any
174161
}
175162

176163
// NewEndpointMap creates a new EndpointMap.
177164
func NewEndpointMap() *EndpointMap {
178165
return &EndpointMap{
179-
endpoints: make(map[*endpointNode]any),
166+
endpoints: make(map[endpointMapKey]endpointData),
167+
}
168+
}
169+
170+
// encodeEndpoint returns a string that uniquely identifies the unordered set of
171+
// addresses within an endpoint.
172+
func encodeEndpoint(e Endpoint) endpointMapKey {
173+
addrs := make([]string, 0, len(e.Addresses))
174+
// base64 encoding the address strings restricts the characters present
175+
// within the strings. This allows us to use a delimiter without the need of
176+
// escape characters.
177+
for _, addr := range e.Addresses {
178+
addrs = append(addrs, base64.StdEncoding.EncodeToString([]byte(addr.Addr)))
180179
}
180+
sort.Strings(addrs)
181+
// " " should not appear in base64 encoded strings.
182+
return endpointMapKey(strings.Join(addrs, " "))
181183
}
182184

183185
// Get returns the value for the address in the map, if present.
184186
func (em *EndpointMap) Get(e Endpoint) (value any, ok bool) {
185-
en := toEndpointNode(e)
186-
if endpoint := em.find(en); endpoint != nil {
187-
return em.endpoints[endpoint], true
187+
val, found := em.endpoints[encodeEndpoint(e)]
188+
if found {
189+
return val.value, true
188190
}
189191
return nil, false
190192
}
191193

192194
// Set updates or adds the value to the address in the map.
193195
func (em *EndpointMap) Set(e Endpoint, value any) {
194-
en := toEndpointNode(e)
195-
if endpoint := em.find(en); endpoint != nil {
196-
em.endpoints[endpoint] = value
197-
return
196+
en := encodeEndpoint(e)
197+
em.endpoints[en] = endpointData{
198+
decodedKey: Endpoint{Addresses: e.Addresses},
199+
value: value,
198200
}
199-
em.endpoints[&en] = value
200201
}
201202

202203
// Len returns the number of entries in the map.
@@ -211,12 +212,8 @@ func (em *EndpointMap) Len() int {
211212
// used for EndpointMap accesses.
212213
func (em *EndpointMap) Keys() []Endpoint {
213214
ret := make([]Endpoint, 0, len(em.endpoints))
214-
for en := range em.endpoints {
215-
var endpoint Endpoint
216-
for addr := range en.addrs {
217-
endpoint.Addresses = append(endpoint.Addresses, Address{Addr: addr})
218-
}
219-
ret = append(ret, endpoint)
215+
for _, en := range em.endpoints {
216+
ret = append(ret, en.decodedKey)
220217
}
221218
return ret
222219
}
@@ -225,27 +222,13 @@ func (em *EndpointMap) Keys() []Endpoint {
225222
func (em *EndpointMap) Values() []any {
226223
ret := make([]any, 0, len(em.endpoints))
227224
for _, val := range em.endpoints {
228-
ret = append(ret, val)
225+
ret = append(ret, val.value)
229226
}
230227
return ret
231228
}
232229

233-
// find returns a pointer to the endpoint node in em if the endpoint node is
234-
// already present. If not found, nil is returned. The comparisons are done on
235-
// the unordered set of addresses within an endpoint.
236-
func (em EndpointMap) find(e endpointNode) *endpointNode {
237-
for endpoint := range em.endpoints {
238-
if e.Equal(endpoint) {
239-
return endpoint
240-
}
241-
}
242-
return nil
243-
}
244-
245230
// Delete removes the specified endpoint from the map.
246231
func (em *EndpointMap) Delete(e Endpoint) {
247-
en := toEndpointNode(e)
248-
if entry := em.find(en); entry != nil {
249-
delete(em.endpoints, entry)
250-
}
232+
en := encodeEndpoint(e)
233+
delete(em.endpoints, en)
251234
}

resolver/map_test.go

+22
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,25 @@ func (s) TestEndpointMap_Values(t *testing.T) {
287287
t.Fatalf("em.Values() returned unexpected elements (-want, +got):\n%v", diff)
288288
}
289289
}
290+
291+
// BenchmarkEndpointMap benchmarks map operations that are expected to run
292+
// faster than O(n). This test doesn't run O(n) operations including listing
293+
// keys and values.
294+
func BenchmarkEndpointMap(b *testing.B) {
295+
em := NewEndpointMap()
296+
for i := range b.N {
297+
em.Set(Endpoint{
298+
Addresses: []Address{{Addr: fmt.Sprintf("%d.%d.%d.%d", i, i, i, i)}},
299+
}, i)
300+
}
301+
for i := range b.N {
302+
em.Get(Endpoint{
303+
Addresses: []Address{{Addr: fmt.Sprintf("%d.%d.%d.%d", i, i, i, i)}},
304+
})
305+
}
306+
for i := range b.N {
307+
em.Delete(Endpoint{
308+
Addresses: []Address{{Addr: fmt.Sprintf("%d.%d.%d.%d", i, i, i, i)}},
309+
})
310+
}
311+
}

0 commit comments

Comments
 (0)