github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/prolly/tree/node_cache.go (about)

     1  // Copyright 2021 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package tree
    16  
    17  import (
    18  	"fmt"
    19  	"sync"
    20  
    21  	"github.com/dolthub/dolt/go/store/hash"
    22  )
    23  
    24  const (
    25  	numStripes      = 32
    26  	stripeMask byte = 0b00011111
    27  )
    28  
    29  func newChunkCache(maxSize int) (c nodeCache) {
    30  	sz := maxSize / numStripes
    31  	for i := range c.stripes {
    32  		c.stripes[i] = newStripe(sz)
    33  	}
    34  	return
    35  }
    36  
    37  type nodeCache struct {
    38  	stripes [numStripes]*stripe
    39  }
    40  
    41  func (c nodeCache) get(addr hash.Hash) (Node, bool) {
    42  	s := c.stripes[addr[0]&stripeMask]
    43  	return s.get(addr)
    44  }
    45  
    46  func (c nodeCache) insert(addr hash.Hash, node Node) {
    47  	s := c.stripes[addr[0]&stripeMask]
    48  	s.insert(addr, node)
    49  }
    50  
    51  type centry struct {
    52  	a    hash.Hash
    53  	n    Node
    54  	i    int
    55  	prev *centry
    56  	next *centry
    57  }
    58  
    59  type stripe struct {
    60  	mu     *sync.Mutex
    61  	chunks map[hash.Hash]*centry
    62  	head   *centry
    63  	sz     int
    64  	maxSz  int
    65  	rev    int
    66  }
    67  
    68  func newStripe(maxSize int) *stripe {
    69  	return &stripe{
    70  		&sync.Mutex{},
    71  		make(map[hash.Hash]*centry),
    72  		nil,
    73  		0,
    74  		maxSize,
    75  		0,
    76  	}
    77  }
    78  
    79  func removeFromList(e *centry) {
    80  	e.prev.next = e.next
    81  	e.next.prev = e.prev
    82  	e.prev = e
    83  	e.next = e
    84  }
    85  
    86  func (s *stripe) moveToFront(e *centry) {
    87  	e.i = s.rev
    88  	s.rev++
    89  	if s.head == e {
    90  		return
    91  	}
    92  	if s.head != nil {
    93  		removeFromList(e)
    94  		e.next = s.head
    95  		e.prev = s.head.prev
    96  		s.head.prev = e
    97  		e.prev.next = e
    98  	}
    99  	s.head = e
   100  }
   101  
   102  func (s *stripe) get(h hash.Hash) (Node, bool) {
   103  	s.mu.Lock()
   104  	defer s.mu.Unlock()
   105  	if e, ok := s.chunks[h]; ok {
   106  		s.moveToFront(e)
   107  		return e.n, true
   108  	} else {
   109  		return Node{}, false
   110  	}
   111  }
   112  
   113  func (s *stripe) insert(addr hash.Hash, node Node) {
   114  	s.mu.Lock()
   115  	defer s.mu.Unlock()
   116  
   117  	if e, ok := s.chunks[addr]; !ok {
   118  		e = &centry{addr, node, 0, nil, nil}
   119  		e.next = e
   120  		e.prev = e
   121  		s.moveToFront(e)
   122  		s.chunks[addr] = e
   123  		s.sz += node.Size()
   124  		s.shrinkToMaxSz()
   125  	} else {
   126  		s.moveToFront(e)
   127  	}
   128  }
   129  
   130  func (s *stripe) shrinkToMaxSz() {
   131  	for s.sz > s.maxSz {
   132  		if s.head != nil {
   133  			t := s.head.prev
   134  			removeFromList(t)
   135  			if t == s.head {
   136  				s.head = nil
   137  			}
   138  			delete(s.chunks, t.a)
   139  			s.sz -= t.n.Size()
   140  		} else {
   141  			panic("cache is empty but cache Size is > than max Size")
   142  		}
   143  	}
   144  }
   145  
   146  func (s *stripe) sanityCheck() {
   147  	if s.head != nil {
   148  		p := s.head.next
   149  		i := 1
   150  		sz := s.head.n.Size()
   151  		lasti := s.head.i
   152  		for p != s.head {
   153  			i++
   154  			sz += p.n.Size()
   155  			if p.i >= lasti {
   156  				panic("encountered lru list entry with higher rev later in the list.")
   157  			}
   158  			p = p.next
   159  		}
   160  		if i != len(s.chunks) {
   161  			panic(fmt.Sprintf("cache lru list has different Size than cache.chunks. %d vs %d", i, len(s.chunks)))
   162  		}
   163  		if sz != s.sz {
   164  			panic("entries reachable from lru list have different Size than cache.sz.")
   165  		}
   166  		j := 1
   167  		p = s.head.prev
   168  		for p != s.head {
   169  			j++
   170  			p = p.prev
   171  		}
   172  		if j != i {
   173  			panic("length of list backwards is not equal to length of list forward")
   174  		}
   175  	} else {
   176  		if len(s.chunks) != 0 {
   177  			panic("lru list is empty but s.chunks is not")
   178  		}
   179  		if s.sz != 0 {
   180  			panic("lru list is empty but s.sz is not 0")
   181  		}
   182  	}
   183  }