mirror of
https://github.com/dolthub/dolt.git
synced 2026-02-11 10:33:08 -06:00
@@ -883,9 +883,9 @@ func TestListDiffLargeWithSameMiddle(t *testing.T) {
|
||||
|
||||
// should only read/write a "small & reasonably sized portion of the total"
|
||||
assert.Equal(95, cs1.Writes)
|
||||
assert.Equal(18, cs1.Reads)
|
||||
assert.Equal(16, cs1.Reads)
|
||||
assert.Equal(85, cs2.Writes)
|
||||
assert.Equal(8, cs2.Reads)
|
||||
assert.Equal(6, cs2.Reads)
|
||||
}
|
||||
|
||||
func TestListTypeAfterMutations(t *testing.T) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/attic-labs/noms/go/chunks"
|
||||
"github.com/attic-labs/noms/go/d"
|
||||
"github.com/attic-labs/noms/go/hash"
|
||||
"github.com/attic-labs/noms/go/util/sizecache"
|
||||
)
|
||||
|
||||
// ValueReader is an interface that knows how to read Noms Values, e.g. datas/Database. Required to avoid import cycle between this package and the package that implements Value reading.
|
||||
@@ -34,11 +35,14 @@ type ValueReadWriter interface {
|
||||
// - all Refs in v point to a Value that can be read from this ValueStore
|
||||
// - all Refs in v point to a Value of the correct Type
|
||||
type ValueStore struct {
|
||||
bs BatchStore
|
||||
cache map[hash.Hash]chunkCacheEntry
|
||||
mu *sync.Mutex
|
||||
bs BatchStore
|
||||
cache map[hash.Hash]chunkCacheEntry
|
||||
mu *sync.Mutex
|
||||
valueCache *sizecache.SizeCache
|
||||
}
|
||||
|
||||
const defaultValueCacheSize = 1 << 25 // 32MB
|
||||
|
||||
type chunkCacheEntry interface {
|
||||
Present() bool
|
||||
Hint() hash.Hash
|
||||
@@ -56,7 +60,11 @@ func newLocalValueStore(cs chunks.ChunkStore) *ValueStore {
|
||||
|
||||
// NewValueStore returns a ValueStore instance that owns the provided BatchStore and manages its lifetime. Calling Close on the returned ValueStore will Close bs.
|
||||
func NewValueStore(bs BatchStore) *ValueStore {
|
||||
return &ValueStore{bs, map[hash.Hash]chunkCacheEntry{}, &sync.Mutex{}}
|
||||
return NewValueStoreWithCache(bs, defaultValueCacheSize)
|
||||
}
|
||||
|
||||
func NewValueStoreWithCache(bs BatchStore, cacheSize uint64) *ValueStore {
|
||||
return &ValueStore{bs, map[hash.Hash]chunkCacheEntry{}, &sync.Mutex{}, sizecache.New(cacheSize)}
|
||||
}
|
||||
|
||||
func (lvs *ValueStore) BatchStore() BatchStore {
|
||||
@@ -65,11 +73,16 @@ func (lvs *ValueStore) BatchStore() BatchStore {
|
||||
|
||||
// ReadValue reads and decodes a value from lvs. It is not considered an error for the requested chunk to be empty; in this case, the function simply returns nil.
|
||||
func (lvs *ValueStore) ReadValue(r hash.Hash) Value {
|
||||
if v, ok := lvs.valueCache.Get(r); ok {
|
||||
return v.(Value)
|
||||
}
|
||||
chunk := lvs.bs.Get(r)
|
||||
if chunk.IsEmpty() {
|
||||
lvs.valueCache.Add(r, 0, nil)
|
||||
return nil
|
||||
}
|
||||
v := DecodeValue(chunk, lvs)
|
||||
lvs.valueCache.Add(r, uint64(len(chunk.Data())), v)
|
||||
|
||||
var entry chunkCacheEntry = absentChunk{}
|
||||
if v != nil {
|
||||
|
||||
78
go/util/sizecache/size_cache.go
Normal file
78
go/util/sizecache/size_cache.go
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright 2016 The Noms Authors. All rights reserved.
|
||||
// Licensed under the Apache License, version 2.0:
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
package sizecache
|
||||
|
||||
// SizeCache implements a simple LRU cache of interface{}-typed key-value pairs. When items are added, the "size" of the item must be provided. LRU items will be expired until the total of all items is below the specified size for the SizeCache
|
||||
import (
|
||||
"container/list"
|
||||
"sync"
|
||||
|
||||
"github.com/attic-labs/noms/go/d"
|
||||
)
|
||||
|
||||
type lruList struct {
|
||||
list.List
|
||||
}
|
||||
|
||||
type sizeCacheEntry struct {
|
||||
size uint64
|
||||
lruEntry *list.Element
|
||||
value interface{}
|
||||
}
|
||||
|
||||
type SizeCache struct {
|
||||
totalSize uint64
|
||||
maxSize uint64
|
||||
mu sync.Mutex
|
||||
lru lruList
|
||||
cache map[interface{}]sizeCacheEntry
|
||||
}
|
||||
|
||||
func New(maxSize uint64) *SizeCache {
|
||||
return &SizeCache{maxSize: maxSize, cache: map[interface{}]sizeCacheEntry{}}
|
||||
}
|
||||
|
||||
func (c *SizeCache) entry(key interface{}) (sizeCacheEntry, bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
entry, ok := c.cache[key]
|
||||
if !ok {
|
||||
return sizeCacheEntry{}, false
|
||||
}
|
||||
c.lru.MoveToBack(entry.lruEntry)
|
||||
return entry, true
|
||||
}
|
||||
|
||||
func (c *SizeCache) Get(key interface{}) (interface{}, bool) {
|
||||
if entry, ok := c.entry(key); ok {
|
||||
return entry.value, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (c *SizeCache) Add(key interface{}, size uint64, value interface{}) {
|
||||
if size <= c.maxSize {
|
||||
if _, ok := c.entry(key); ok {
|
||||
return
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
newEl := c.lru.PushBack(key)
|
||||
ce := sizeCacheEntry{size: size, lruEntry: newEl, value: value}
|
||||
c.cache[key] = ce
|
||||
c.totalSize += ce.size
|
||||
for el := c.lru.Front(); el != nil && c.totalSize > c.maxSize; {
|
||||
key1 := el.Value
|
||||
ce, ok := c.cache[key1]
|
||||
d.Chk.True(ok, "SizeCache is missing expected value")
|
||||
next := el.Next()
|
||||
delete(c.cache, key1)
|
||||
c.totalSize -= ce.size
|
||||
c.lru.Remove(el)
|
||||
el = next
|
||||
}
|
||||
}
|
||||
}
|
||||
88
go/util/sizecache/size_cache_test.go
Normal file
88
go/util/sizecache/size_cache_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package sizecache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/attic-labs/noms/go/hash"
|
||||
"github.com/attic-labs/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
valueCounter = 0
|
||||
)
|
||||
|
||||
func hashFromString(s string) hash.Hash {
|
||||
return hash.FromData([]byte(s))
|
||||
}
|
||||
|
||||
func TestSizeCache(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
defSize := uint64(200)
|
||||
|
||||
c := New(1024)
|
||||
for i, v := range []string{"data-1", "data-2", "data-3", "data-4", "data-5", "data-6", "data-7", "data-8", "data-9"} {
|
||||
c.Add(hashFromString(v), defSize, v)
|
||||
maxElements := uint64(i + 1)
|
||||
if maxElements >= uint64(5) {
|
||||
maxElements = uint64(5)
|
||||
}
|
||||
assert.Equal(maxElements*defSize, c.totalSize)
|
||||
}
|
||||
|
||||
_, ok := c.Get(hashFromString("data-1"))
|
||||
assert.False(ok)
|
||||
assert.Equal(hashFromString("data-5"), c.lru.Front().Value)
|
||||
|
||||
v, ok := c.Get(hashFromString("data-5"))
|
||||
assert.True(ok)
|
||||
assert.Equal("data-5", v.(string))
|
||||
assert.Equal(hashFromString("data-5"), c.lru.Back().Value)
|
||||
assert.Equal(hashFromString("data-6"), c.lru.Front().Value)
|
||||
|
||||
c.Add(hashFromString("data-7"), defSize, "data-7")
|
||||
assert.Equal(hashFromString("data-7"), c.lru.Back().Value)
|
||||
assert.Equal(uint64(1000), c.totalSize)
|
||||
|
||||
c.Add(hashFromString("no-data"), 0, nil)
|
||||
v, ok = c.Get(hashFromString("no-data"))
|
||||
assert.True(ok)
|
||||
assert.Nil(v)
|
||||
assert.Equal(hashFromString("no-data"), c.lru.Back().Value)
|
||||
assert.Equal(uint64(1000), c.totalSize)
|
||||
assert.Equal(6, c.lru.Len())
|
||||
assert.Equal(6, len(c.cache))
|
||||
|
||||
for _, v := range []string{"data-5", "data-6", "data-7", "data-8", "data-9"} {
|
||||
c.Get(hashFromString(v))
|
||||
assert.Equal(hashFromString(v), c.lru.Back().Value)
|
||||
}
|
||||
assert.Equal(hashFromString("no-data"), c.lru.Front().Value)
|
||||
|
||||
c.Add(hashFromString("data-10"), 200, "data-10")
|
||||
assert.Equal(uint64(1000), c.totalSize)
|
||||
assert.Equal(5, c.lru.Len())
|
||||
assert.Equal(5, len(c.cache))
|
||||
|
||||
_, ok = c.Get(hashFromString("no-data"))
|
||||
assert.False(ok)
|
||||
_, ok = c.Get(hashFromString("data-5"))
|
||||
assert.False(ok)
|
||||
}
|
||||
|
||||
func TestTooLargeValue(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
c := New(1024)
|
||||
c.Add(hashFromString("big-data"), 2048, "big-data")
|
||||
_, ok := c.Get(hashFromString("big-data"))
|
||||
assert.False(ok)
|
||||
}
|
||||
|
||||
func TestZeroSizeCache(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
c := New(0)
|
||||
c.Add(hashFromString("data1"), 200, "data1")
|
||||
_, ok := c.Get(hashFromString("data1"))
|
||||
assert.False(ok)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
@@ -15,7 +16,6 @@ import (
|
||||
"github.com/attic-labs/noms/go/d"
|
||||
"github.com/attic-labs/noms/go/datas"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
Reference in New Issue
Block a user