Browse Source

Merge pull request #456 from abustany/faster-stmt-lru

Use an LRU cache with "pure" string keys
Chris Bannister 10 years ago
parent
commit
d4969a7717
4 changed files with 197 additions and 2 deletions
  1. 1 1
      cluster.go
  2. 123 0
      lru/lru.go
  3. 72 0
      lru/lru_test.go
  4. 1 1
      session.go

+ 1 - 1
cluster.go

@@ -9,7 +9,7 @@ import (
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
-	"github.com/golang/groupcache/lru"
+	"github.com/gocql/gocql/lru"
 )
 )
 
 
 const defaultMaxPreparedStmts = 1000
 const defaultMaxPreparedStmts = 1000

+ 123 - 0
lru/lru.go

@@ -0,0 +1,123 @@
+/*
+Copyright 2015 To gocql authors
+Copyright 2013 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package lru implements an LRU cache.
+package lru
+
+import "container/list"
+
+// Cache is an LRU cache. It is not safe for concurrent access.
+//
+// This cache has been forked from github.com/golang/groupcache/lru, but
+// specialized with string keys to avoid the allocations caused by wrapping them
+// in interface{}.
+type Cache struct {
+	// MaxEntries is the maximum number of cache entries before
+	// an item is evicted. Zero means no limit.
+	MaxEntries int
+
+	// OnEvicted optionally specificies a callback function to be
+	// executed when an entry is purged from the cache.
+	OnEvicted func(key string, value interface{})
+
+	ll    *list.List
+	cache map[string]*list.Element
+}
+
+type entry struct {
+	key   string
+	value interface{}
+}
+
+// New creates a new Cache.
+// If maxEntries is zero, the cache has no limit and it's assumed
+// that eviction is done by the caller.
+func New(maxEntries int) *Cache {
+	return &Cache{
+		MaxEntries: maxEntries,
+		ll:         list.New(),
+		cache:      make(map[string]*list.Element),
+	}
+}
+
+// Add adds a value to the cache.
+func (c *Cache) Add(key string, value interface{}) {
+	if c.cache == nil {
+		c.cache = make(map[string]*list.Element)
+		c.ll = list.New()
+	}
+	if ee, ok := c.cache[key]; ok {
+		c.ll.MoveToFront(ee)
+		ee.Value.(*entry).value = value
+		return
+	}
+	ele := c.ll.PushFront(&entry{key, value})
+	c.cache[key] = ele
+	if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries {
+		c.RemoveOldest()
+	}
+}
+
+// Get looks up a key's value from the cache.
+func (c *Cache) Get(key string) (value interface{}, ok bool) {
+	if c.cache == nil {
+		return
+	}
+	if ele, hit := c.cache[key]; hit {
+		c.ll.MoveToFront(ele)
+		return ele.Value.(*entry).value, true
+	}
+	return
+}
+
+// Remove removes the provided key from the cache.
+func (c *Cache) Remove(key string) {
+	if c.cache == nil {
+		return
+	}
+	if ele, hit := c.cache[key]; hit {
+		c.removeElement(ele)
+	}
+}
+
+// RemoveOldest removes the oldest item from the cache.
+func (c *Cache) RemoveOldest() {
+	if c.cache == nil {
+		return
+	}
+	ele := c.ll.Back()
+	if ele != nil {
+		c.removeElement(ele)
+	}
+}
+
+func (c *Cache) removeElement(e *list.Element) {
+	c.ll.Remove(e)
+	kv := e.Value.(*entry)
+	delete(c.cache, kv.key)
+	if c.OnEvicted != nil {
+		c.OnEvicted(kv.key, kv.value)
+	}
+}
+
+// Len returns the number of items in the cache.
+func (c *Cache) Len() int {
+	if c.cache == nil {
+		return 0
+	}
+	return c.ll.Len()
+}

+ 72 - 0
lru/lru_test.go

@@ -0,0 +1,72 @@
+/*
+Copyright 2015 To gocql authors
+Copyright 2013 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package lru
+
+import (
+	"testing"
+)
+
+type simpleStruct struct {
+	int
+	string
+}
+
+type complexStruct struct {
+	int
+	simpleStruct
+}
+
+var getTests = []struct {
+	name       string
+	keyToAdd   string
+	keyToGet   string
+	expectedOk bool
+}{
+	{"string_hit", "mystring", "mystring", true},
+	{"string_miss", "mystring", "nonsense", false},
+	{"simple_struct_hit", "two", "two", true},
+	{"simeple_struct_miss", "two", "noway", false},
+}
+
+func TestGet(t *testing.T) {
+	for _, tt := range getTests {
+		lru := New(0)
+		lru.Add(tt.keyToAdd, 1234)
+		val, ok := lru.Get(tt.keyToGet)
+		if ok != tt.expectedOk {
+			t.Fatalf("%s: cache hit = %v; want %v", tt.name, ok, !ok)
+		} else if ok && val != 1234 {
+			t.Fatalf("%s expected get to return 1234 but got %v", tt.name, val)
+		}
+	}
+}
+
+func TestRemove(t *testing.T) {
+	lru := New(0)
+	lru.Add("mystring", 1234)
+	if val, ok := lru.Get("mystring"); !ok {
+		t.Fatal("TestRemove returned no match")
+	} else if val != 1234 {
+		t.Fatalf("TestRemove failed.  Expected %d, got %v", 1234, val)
+	}
+
+	lru.Remove("mystring")
+	if _, ok := lru.Get("mystring"); ok {
+		t.Fatal("TestRemove returned a removed entry")
+	}
+}

+ 1 - 1
session.go

@@ -15,7 +15,7 @@ import (
 	"time"
 	"time"
 	"unicode"
 	"unicode"
 
 
-	"github.com/golang/groupcache/lru"
+	"github.com/gocql/gocql/lru"
 )
 )
 
 
 // Session is the interface used by users to interact with the database.
 // Session is the interface used by users to interact with the database.