@@ -19,7 +19,9 @@ package cache // import "github.com/cockroachdb/pebble/internal/cache"
19
19
20
20
import (
21
21
"fmt"
22
+ "os"
22
23
"runtime"
24
+ "runtime/debug"
23
25
"sync"
24
26
"sync/atomic"
25
27
@@ -37,6 +39,17 @@ type key struct {
37
39
offset uint64
38
40
}
39
41
42
+ // file returns the "file key" for the receiver. This is the key used for the
43
+ // shard.files map.
44
+ func (k key ) file () key {
45
+ k .offset = 0
46
+ return k
47
+ }
48
+
49
+ func (k key ) String () string {
50
+ return fmt .Sprintf ("%d/%d/%d" , k .id , k .fileNum , k .offset )
51
+ }
52
+
40
53
// Handle provides a strong reference to an entry in the cache. The reference
41
54
// does not pin the entry in the cache, but it does prevent the underlying byte
42
55
// slice from being reused. When entry is non-nil, value is initialized to
@@ -107,8 +120,17 @@ type shard struct {
107
120
reservedSize int64
108
121
maxSize int64
109
122
coldTarget int64
110
- blocks map [key ]* entry // fileNum+offset -> block
111
- files map [fileKey ]* entry // fileNum -> list of blocks
123
+ blocks robinHoodMap // fileNum+offset -> block
124
+ files robinHoodMap // fileNum -> list of blocks
125
+
126
+ // The blocks and files maps store values in manually managed memory that is
127
+ // invisible to the Go GC. This is fine for Value and entry objects that are
128
+ // stored in manually managed memory. Auto Values and the associated auto
129
+ // entries need to have a reference that the Go GC is aware of to prevent
130
+ // them from being reclaimed. The entries map provides this reference. When
131
+ // the "invariants" build tag is set, all Value and entry objects are Go
132
+ // allocated and the entries map will contain a reference to every entry.
133
+ entries map [* entry ]struct {}
112
134
113
135
handHot * entry
114
136
handCold * entry
@@ -121,7 +143,7 @@ type shard struct {
121
143
122
144
func (c * shard ) Get (id , fileNum , offset uint64 ) Handle {
123
145
c .mu .RLock ()
124
- e := c .blocks [ key {fileKey {id , fileNum }, offset }]
146
+ e := c .blocks . Get ( key {fileKey {id , fileNum }, offset })
125
147
var value * Value
126
148
if e != nil {
127
149
value = e .getValue ()
@@ -160,7 +182,7 @@ func (c *shard) Set(id, fileNum, offset uint64, value *Value) Handle {
160
182
defer c .mu .Unlock ()
161
183
162
184
k := key {fileKey {id , fileNum }, offset }
163
- e := c .blocks [ k ]
185
+ e := c .blocks . Get ( k )
164
186
if e != nil && e .manual != value .manual () {
165
187
panic (fmt .Sprintf ("pebble: inconsistent caching of manual Value: entry=%t vs value=%t" ,
166
188
e .manual , value .manual ()))
@@ -231,7 +253,7 @@ func (c *shard) Delete(id, fileNum, offset uint64) {
231
253
c .mu .Lock ()
232
254
defer c .mu .Unlock ()
233
255
234
- e := c .blocks [ key {fileKey {id , fileNum }, offset }]
256
+ e := c .blocks . Get ( key {fileKey {id , fileNum }, offset })
235
257
if e == nil {
236
258
return
237
259
}
@@ -243,7 +265,8 @@ func (c *shard) EvictFile(id, fileNum uint64) {
243
265
c .mu .Lock ()
244
266
defer c .mu .Unlock ()
245
267
246
- blocks := c .files [fileKey {id , fileNum }]
268
+ fkey := key {fileKey {id , fileNum }, 0 }
269
+ blocks := c .files .Get (fkey )
247
270
if blocks == nil {
248
271
return
249
272
}
@@ -291,7 +314,12 @@ func (c *shard) metaAdd(key key, e *entry) bool {
291
314
return false
292
315
}
293
316
294
- c .blocks [key ] = e
317
+ c .blocks .Put (key , e )
318
+ if ! e .managed {
319
+ // Go allocated entries need to be referenced from Go memory. The entries
320
+ // map provides that reference.
321
+ c .entries [e ] = struct {}{}
322
+ }
295
323
296
324
if c .handHot == nil {
297
325
// first element
@@ -306,8 +334,9 @@ func (c *shard) metaAdd(key key, e *entry) bool {
306
334
c .handCold = c .handCold .prev ()
307
335
}
308
336
309
- if fileBlocks := c .files [key .fileKey ]; fileBlocks == nil {
310
- c .files [key .fileKey ] = e
337
+ fkey := key .file ()
338
+ if fileBlocks := c .files .Get (fkey ); fileBlocks == nil {
339
+ c .files .Put (fkey , e )
311
340
} else {
312
341
fileBlocks .linkFile (e )
313
342
}
@@ -323,7 +352,12 @@ func (c *shard) metaDel(e *entry) {
323
352
}
324
353
e .setValue (nil )
325
354
326
- delete (c .blocks , e .key )
355
+ c .blocks .Delete (e .key )
356
+ if ! e .managed {
357
+ // Go allocated entries need to be referenced from Go memory. The entries
358
+ // map provides that reference.
359
+ delete (c .entries , e )
360
+ }
327
361
328
362
if e == c .handHot {
329
363
c .handHot = c .handHot .prev ()
@@ -342,10 +376,11 @@ func (c *shard) metaDel(e *entry) {
342
376
c .handTest = nil
343
377
}
344
378
379
+ fkey := e .key .file ()
345
380
if next := e .unlinkFile (); e == next {
346
- delete ( c .files , e . key . fileKey )
381
+ c .files . Delete ( fkey )
347
382
} else {
348
- c .files [ e . key . fileKey ] = next
383
+ c .files . Put ( fkey , next )
349
384
}
350
385
351
386
c .metaCheck (e )
@@ -354,21 +389,28 @@ func (c *shard) metaDel(e *entry) {
354
389
// Check that the specified entry is not referenced by the cache.
355
390
func (c * shard ) metaCheck (e * entry ) {
356
391
if invariants .Enabled {
357
- for _ , t := range c . blocks {
358
- if e == t {
359
- panic ( "not reached" )
360
- }
392
+ if _ , ok := c . entries [ e ]; ok {
393
+ fmt . Fprintf ( os . Stderr , "%p: %s unexpectedly found in entries map \n %s" ,
394
+ e , e . key , debug . Stack () )
395
+ os . Exit ( 1 )
361
396
}
362
- for _ , t := range c .files {
363
- if e == t {
364
- panic ("not reached" )
365
- }
397
+ if c .blocks .findByValue (e ) != nil {
398
+ fmt .Fprintf (os .Stderr , "%p: %s unexpectedly found in blocks map\n %s\n %s" ,
399
+ e , e .key , & c .blocks , debug .Stack ())
400
+ os .Exit (1 )
401
+ }
402
+ if c .files .findByValue (e ) != nil {
403
+ fmt .Fprintf (os .Stderr , "%p: %s unexpectedly found in files map\n %s\n %s" ,
404
+ e , e .key , & c .files , debug .Stack ())
405
+ os .Exit (1 )
366
406
}
367
407
// NB: c.hand{Hot,Cold,Test} are pointers into a single linked list. We
368
408
// only have to traverse one of them to check all of them.
369
409
for t := c .handHot .next (); t != c .handHot ; t = t .next () {
370
410
if e == t {
371
- panic ("not reached" )
411
+ fmt .Fprintf (os .Stderr , "%p: %s unexpectedly found in blocks list\n %s" ,
412
+ e , e .key , debug .Stack ())
413
+ os .Exit (1 )
372
414
}
373
415
}
374
416
}
@@ -553,6 +595,8 @@ func clearCache(obj interface{}) {
553
595
s .mu .Lock ()
554
596
s .maxSize = 0
555
597
s .evict ()
598
+ s .blocks .free ()
599
+ s .files .free ()
556
600
s .mu .Unlock ()
557
601
}
558
602
}
@@ -567,9 +611,10 @@ func newShards(size int64, shards int) *Cache {
567
611
c .shards [i ] = shard {
568
612
maxSize : size / int64 (len (c .shards )),
569
613
coldTarget : size / int64 (len (c .shards )),
570
- blocks : make (map [key ]* entry ),
571
- files : make (map [fileKey ]* entry ),
614
+ entries : make (map [* entry ]struct {}),
572
615
}
616
+ c .shards [i ].blocks .init (16 )
617
+ c .shards [i ].files .init (16 )
573
618
}
574
619
// TODO(peter): This finalizer is used to clear the cache when the Cache
575
620
// itself is GC'd. Investigate making this explicit, and then changing the
@@ -703,7 +748,7 @@ func (c *Cache) Metrics() Metrics {
703
748
for i := range c .shards {
704
749
s := & c .shards [i ]
705
750
s .mu .RLock ()
706
- m .Count += int64 (len ( s .blocks ))
751
+ m .Count += int64 (s .blocks . Count ( ))
707
752
m .Size += s .sizeHot + s .sizeCold
708
753
s .mu .RUnlock ()
709
754
m .Hits += atomic .LoadInt64 (& s .hits )
0 commit comments