Skip to content

Commit 8a3fe0c

Browse files
authored
Merge pull request #653 from upper/refactor-memory-and-speed-optimizations
Memory and speed optimizations
2 parents 82a1771 + ba90d64 commit 8a3fe0c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1177
-2123
lines changed

adapter/cockroachdb/database.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ func (*database) CompileStatement(sess sqladapter.Session, stmt *exql.Statement,
129129
}
130130

131131
query, args := sqlbuilder.Preprocess(compiled, args)
132-
query = sqladapter.ReplaceWithDollarSign(query)
132+
query = string(sqladapter.ReplaceWithDollarSign([]byte(query)))
133133
return query, args, nil
134134
}
135135

adapter/postgresql/database.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ func (*database) CompileStatement(sess sqladapter.Session, stmt *exql.Statement,
9999
}
100100

101101
query, args := sqlbuilder.Preprocess(compiled, args)
102-
query = sqladapter.ReplaceWithDollarSign(query)
102+
query = string(sqladapter.ReplaceWithDollarSign([]byte(query)))
103103
return query, args, nil
104104
}
105105

adapter/ql/database.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func (*database) CompileStatement(sess sqladapter.Session, stmt *exql.Statement,
8181
}
8282

8383
query, args := sqlbuilder.Preprocess(compiled, args)
84-
query = sqladapter.ReplaceWithDollarSign(query)
84+
query = string(sqladapter.ReplaceWithDollarSign([]byte(query)))
8585
return query, args, nil
8686
}
8787

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ require (
1111
github.com/jackc/pgx/v4 v4.15.0
1212
github.com/lib/pq v1.10.4
1313
github.com/mattn/go-sqlite3 v1.14.9
14+
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
15+
github.com/segmentio/fasthash v1.0.3
1416
github.com/sirupsen/logrus v1.8.1
1517
github.com/stretchr/testify v1.7.0
1618
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
100100
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
101101
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
102102
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
103+
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
104+
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
103105
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
104106
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
105107
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -112,6 +114,8 @@ github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
112114
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
113115
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
114116
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
117+
github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM=
118+
github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY=
115119
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
116120
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
117121
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=

internal/cache/cache.go

Lines changed: 34 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,21 @@ package cache
2424
import (
2525
"container/list"
2626
"errors"
27-
"fmt"
28-
"strconv"
2927
"sync"
30-
31-
"github.com/upper/db/v4/internal/cache/hashstructure"
3228
)
3329

3430
const defaultCapacity = 128
3531

3632
// Cache holds a map of volatile key -> values.
3733
type Cache struct {
38-
cache map[string]*list.Element
39-
li *list.List
40-
capacity int
34+
keys *list.List
35+
items map[uint64]*list.Element
4136
mu sync.RWMutex
37+
capacity int
4238
}
4339

44-
type item struct {
45-
key string
40+
type cacheItem struct {
41+
key uint64
4642
value interface{}
4743
}
4844

@@ -52,11 +48,11 @@ func NewCacheWithCapacity(capacity int) (*Cache, error) {
5248
if capacity < 1 {
5349
return nil, errors.New("Capacity must be greater than zero.")
5450
}
55-
return &Cache{
56-
cache: make(map[string]*list.Element),
57-
li: list.New(),
51+
c := &Cache{
5852
capacity: capacity,
59-
}, nil
53+
}
54+
c.init()
55+
return c, nil
6056
}
6157

6258
// NewCache initializes a new caching space with default settings.
@@ -68,6 +64,11 @@ func NewCache() *Cache {
6864
return c
6965
}
7066

67+
func (c *Cache) init() {
68+
c.items = make(map[uint64]*list.Element)
69+
c.keys = list.New()
70+
}
71+
7172
// Read attempts to retrieve a cached value as a string, if the value does not
7273
// exists returns an empty string and false.
7374
func (c *Cache) Read(h Hashable) (string, bool) {
@@ -84,33 +85,35 @@ func (c *Cache) Read(h Hashable) (string, bool) {
8485
func (c *Cache) ReadRaw(h Hashable) (interface{}, bool) {
8586
c.mu.RLock()
8687
defer c.mu.RUnlock()
87-
data, ok := c.cache[h.Hash()]
88+
89+
item, ok := c.items[h.Hash()]
8890
if ok {
89-
return data.Value.(*item).value, true
91+
return item.Value.(*cacheItem).value, true
9092
}
93+
9194
return nil, false
9295
}
9396

9497
// Write stores a value in memory. If the value already exists its overwritten.
9598
func (c *Cache) Write(h Hashable, value interface{}) {
96-
key := h.Hash()
97-
9899
c.mu.Lock()
99100
defer c.mu.Unlock()
100101

101-
if el, ok := c.cache[key]; ok {
102-
el.Value.(*item).value = value
103-
c.li.MoveToFront(el)
102+
key := h.Hash()
103+
104+
if item, ok := c.items[key]; ok {
105+
item.Value.(*cacheItem).value = value
106+
c.keys.MoveToFront(item)
104107
return
105108
}
106109

107-
c.cache[key] = c.li.PushFront(&item{key, value})
110+
c.items[key] = c.keys.PushFront(&cacheItem{key, value})
108111

109-
for c.li.Len() > c.capacity {
110-
el := c.li.Remove(c.li.Back())
111-
delete(c.cache, el.(*item).key)
112-
if p, ok := el.(*item).value.(HasOnPurge); ok {
113-
p.OnPurge()
112+
for c.keys.Len() > c.capacity {
113+
item := c.keys.Remove(c.keys.Back()).(*cacheItem)
114+
delete(c.items, item.key)
115+
if p, ok := item.value.(HasOnEvict); ok {
116+
p.OnEvict()
114117
}
115118
}
116119
}
@@ -120,33 +123,12 @@ func (c *Cache) Write(h Hashable, value interface{}) {
120123
func (c *Cache) Clear() {
121124
c.mu.Lock()
122125
defer c.mu.Unlock()
123-
for _, el := range c.cache {
124-
if p, ok := el.Value.(*item).value.(HasOnPurge); ok {
125-
p.OnPurge()
126-
}
127-
}
128-
c.cache = make(map[string]*list.Element)
129-
c.li.Init()
130-
}
131126

132-
// Hash returns a hash of the given struct.
133-
func Hash(v interface{}) string {
134-
q, err := hashstructure.Hash(v, nil)
135-
if err != nil {
136-
panic(fmt.Sprintf("Could not hash struct: %v", err.Error()))
127+
for _, item := range c.items {
128+
if p, ok := item.Value.(*cacheItem).value.(HasOnEvict); ok {
129+
p.OnEvict()
130+
}
137131
}
138-
return strconv.FormatUint(q, 10)
139-
}
140-
141-
type hash struct {
142-
name string
143-
}
144-
145-
func (h *hash) Hash() string {
146-
return h.name
147-
}
148132

149-
// String returns a Hashable that produces a hash equal to the given string.
150-
func String(s string) Hashable {
151-
return &hash{s}
133+
c.init()
152134
}

internal/cache/cache_test.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ package cache
2323

2424
import (
2525
"fmt"
26+
"hash/fnv"
2627
"testing"
2728
)
2829

@@ -32,8 +33,10 @@ type cacheableT struct {
3233
Name string
3334
}
3435

35-
func (ct *cacheableT) Hash() string {
36-
return Hash(ct)
36+
func (ct *cacheableT) Hash() uint64 {
37+
s := fnv.New64()
38+
s.Sum([]byte(ct.Name))
39+
return s.Sum64()
3740
}
3841

3942
var (
@@ -77,6 +80,13 @@ func BenchmarkNewCache(b *testing.B) {
7780
}
7881
}
7982

83+
func BenchmarkNewCacheAndClear(b *testing.B) {
84+
for i := 0; i < b.N; i++ {
85+
c := NewCache()
86+
c.Clear()
87+
}
88+
}
89+
8090
func BenchmarkReadNonExistentValue(b *testing.B) {
8191
z := NewCache()
8292
for i := 0; i < b.N; i++ {

internal/cache/hash.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package cache
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/segmentio/fasthash/fnv1a"
7+
)
8+
9+
const (
10+
hashTypeInt uint64 = 1 << iota
11+
hashTypeSignedInt
12+
hashTypeBool
13+
hashTypeString
14+
hashTypeHashable
15+
hashTypeNil
16+
)
17+
18+
type hasher struct {
19+
t uint64
20+
v interface{}
21+
}
22+
23+
func (h *hasher) Hash() uint64 {
24+
return NewHash(h.t, h.v)
25+
}
26+
27+
func NewHashable(t uint64, v interface{}) Hashable {
28+
return &hasher{t: t, v: v}
29+
}
30+
31+
func InitHash(t uint64) uint64 {
32+
return fnv1a.AddUint64(fnv1a.Init64, t)
33+
}
34+
35+
func NewHash(t uint64, in ...interface{}) uint64 {
36+
return AddToHash(InitHash(t), in...)
37+
}
38+
39+
func AddToHash(h uint64, in ...interface{}) uint64 {
40+
for i := range in {
41+
if in[i] == nil {
42+
continue
43+
}
44+
h = addToHash(h, in[i])
45+
}
46+
return h
47+
}
48+
49+
func addToHash(h uint64, in interface{}) uint64 {
50+
switch v := in.(type) {
51+
case uint64:
52+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeInt), v)
53+
case uint32:
54+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeInt), uint64(v))
55+
case uint16:
56+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeInt), uint64(v))
57+
case uint8:
58+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeInt), uint64(v))
59+
case uint:
60+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeInt), uint64(v))
61+
case int64:
62+
if v < 0 {
63+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeSignedInt), uint64(-v))
64+
} else {
65+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeInt), uint64(v))
66+
}
67+
case int32:
68+
if v < 0 {
69+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeSignedInt), uint64(-v))
70+
} else {
71+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeInt), uint64(v))
72+
}
73+
case int16:
74+
if v < 0 {
75+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeSignedInt), uint64(-v))
76+
} else {
77+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeInt), uint64(v))
78+
}
79+
case int8:
80+
if v < 0 {
81+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeSignedInt), uint64(-v))
82+
} else {
83+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeInt), uint64(v))
84+
}
85+
case int:
86+
if v < 0 {
87+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeSignedInt), uint64(-v))
88+
} else {
89+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeInt), uint64(v))
90+
}
91+
case bool:
92+
if v {
93+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeBool), 1)
94+
} else {
95+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeBool), 2)
96+
}
97+
case string:
98+
return fnv1a.AddString64(fnv1a.AddUint64(h, hashTypeString), v)
99+
case Hashable:
100+
if in == nil {
101+
panic(fmt.Sprintf("could not hash nil element %T", in))
102+
}
103+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeHashable), v.Hash())
104+
case nil:
105+
return fnv1a.AddUint64(fnv1a.AddUint64(h, hashTypeNil), 0)
106+
default:
107+
panic(fmt.Sprintf("unsupported value type %T", in))
108+
}
109+
}

internal/cache/hashstructure/LICENSE

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)