├── .gitignore
├── .idea
├── .gitignore
├── mcache.iml
├── modules.xml
└── vcs.xml
├── 2q.go
├── 2q_test.go
├── LICENSE
├── README.md
├── arc.go
├── arc_test.go
├── go.mod
├── go.sum
├── hashLfu.go
├── hashLfu_test.go
├── hashLru.go
├── hashLru_test.go
├── lfu.go
├── lfu_test.go
├── lru.go
├── lru_test.go
├── simplelfu
├── lfu.go
├── lfu_interface.go
└── lfu_test.go
├── simplelru
├── lru.go
├── lru_interface.go
└── lru_test.go
└── tool.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 | *.ipr
8 | *.iml
9 | *.iws
10 | *.log
11 | *.cache
12 | *.diff
13 | *.patch
14 | *.tmp
15 |
16 | # Test binary, built with `go test -c`
17 | *.test
18 |
19 | # Output of the go coverage tool, specifically when used with LiteIDE
20 | *.out
21 |
22 | # Dependency directories (remove the comment below to include it)
23 | # vendor/
24 |
25 | .DS_Store
26 | .idea
27 | .project
28 | .classpath
29 | .settings/
30 | target/
31 | dependency-reduced-pom.xml
32 | pom.xml.versionsBackup
33 | .gradle/
34 | build/
35 | .idea/
36 | Thumbs.db
37 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/.idea/mcache.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/2q.go:
--------------------------------------------------------------------------------
1 | package mcache
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 |
7 | "github.com/songangweb/mcache/simplelru"
8 | )
9 |
10 | const (
11 | // Default2QRecentRatio is the ratio of the 2Q cache dedicated
12 | // to recently added entries that have only been accessed once.
13 | Default2QRecentRatio = 0.25
14 |
15 | // Default2QGhostEntries is the default ratio of ghost
16 | // entries kept to track entries recently evicted
17 | Default2QGhostEntries = 0.50
18 | )
19 |
20 | // TwoQueueCache is a thread-safe fixed size 2Q cache.
21 | // 2Q is an enhancement over the standard LRU cache
22 | // in that it tracks both frequently and recently used
23 | // entries separately. This avoids a burst in access to new
24 | // entries from evicting frequently used entries. It adds some
25 | // additional tracking overhead to the standard LRU cache, and is
26 | // computationally about 2x the cost, and adds some metadata over
27 | // head. The ARCCache is similar, but does not require setting any
28 | // parameters.
29 | type TwoQueueCache struct {
30 | size int
31 | recentSize int
32 |
33 | recent simplelru.LRUCache
34 | frequent simplelru.LRUCache
35 | recentEvict simplelru.LRUCache
36 | lock sync.RWMutex
37 | }
38 |
39 | // New2Q creates a new TwoQueueCache using the default
40 | // values for the parameters.
41 | func New2Q(size int) (*TwoQueueCache, error) {
42 | return New2QParams(size, Default2QRecentRatio, Default2QGhostEntries)
43 | }
44 |
45 | // New2QParams creates a new TwoQueueCache using the provided
46 | // parameter values.
47 | func New2QParams(size int, recentRatio float64, ghostRatio float64) (*TwoQueueCache, error) {
48 | if size <= 0 {
49 | return nil, fmt.Errorf("invalid size")
50 | }
51 | if recentRatio < 0.0 || recentRatio > 1.0 {
52 | return nil, fmt.Errorf("invalid recent ratio")
53 | }
54 | if ghostRatio < 0.0 || ghostRatio > 1.0 {
55 | return nil, fmt.Errorf("invalid ghost ratio")
56 | }
57 |
58 | // Determine the sub-sizes
59 | recentSize := int(float64(size) * recentRatio)
60 | evictSize := int(float64(size) * ghostRatio)
61 |
62 | // Allocate the LRUs
63 | recent, err := simplelru.NewLRU(size, nil)
64 | if err != nil {
65 | return nil, err
66 | }
67 | frequent, err := simplelru.NewLRU(size, nil)
68 | if err != nil {
69 | return nil, err
70 | }
71 | recentEvict, err := simplelru.NewLRU(evictSize, nil)
72 | if err != nil {
73 | return nil, err
74 | }
75 |
76 | // Initialize the cache
77 | c := &TwoQueueCache{
78 | size: size,
79 | recentSize: recentSize,
80 | recent: recent,
81 | frequent: frequent,
82 | recentEvict: recentEvict,
83 | }
84 | return c, nil
85 | }
86 |
87 | // Get looks up a key's value from the cache.
88 | func (c *TwoQueueCache) Get(key interface{}) (value interface{}, expirationTime int64, ok bool) {
89 | c.lock.Lock()
90 | defer c.lock.Unlock()
91 |
92 | // Check if this is a frequent value
93 | if val, expirationTime, ok := c.frequent.Get(key); ok {
94 | return val, expirationTime, ok
95 | }
96 |
97 | // If the value is contained in recent, then we
98 | // promote it to frequent
99 | if val, expirationTime, ok := c.recent.Peek(key); ok {
100 | c.recent.Remove(key)
101 | c.frequent.Add(key, val, expirationTime)
102 | return val, expirationTime, ok
103 | }
104 |
105 | // No hit
106 | return nil, expirationTime, false
107 | }
108 |
109 | // Add adds a value to the cache.
110 | func (c *TwoQueueCache) Add(key, value interface{}, expirationTime int64,) {
111 | c.lock.Lock()
112 | defer c.lock.Unlock()
113 |
114 | // Check if the value is frequently used already,
115 | // and just update the value
116 | if c.frequent.Contains(key) {
117 | c.frequent.Add(key, value, expirationTime)
118 | return
119 | }
120 |
121 | // Check if the value is recently used, and promote
122 | // the value into the frequent list
123 | if c.recent.Contains(key) {
124 | c.recent.Remove(key)
125 | c.frequent.Add(key, value, expirationTime)
126 | return
127 | }
128 |
129 | // If the value was recently evicted, add it to the
130 | // frequently used list
131 | if c.recentEvict.Contains(key) {
132 | c.ensureSpace(true)
133 | c.recentEvict.Remove(key)
134 | c.frequent.Add(key, value, expirationTime)
135 | return
136 | }
137 |
138 | // Add to the recently seen list
139 | c.ensureSpace(false)
140 | c.recent.Add(key, value, expirationTime)
141 | return
142 | }
143 |
144 | // ensureSpace is used to ensure we have space in the cache
145 | func (c *TwoQueueCache) ensureSpace(recentEvict bool) {
146 | // If we have space, nothing to do
147 | recentLen := c.recent.Len()
148 | freqLen := c.frequent.Len()
149 | if recentLen+freqLen < c.size {
150 | return
151 | }
152 |
153 | // If the recent buffer is larger than
154 | // the target, evict from there
155 | if recentLen > 0 && (recentLen > c.recentSize || (recentLen == c.recentSize && !recentEvict)) {
156 | k, _, _, _ := c.recent.RemoveOldest()
157 | c.recentEvict.Add(k, nil, 0)
158 | return
159 | }
160 |
161 | // Remove from the frequent list otherwise
162 | c.frequent.RemoveOldest()
163 | }
164 |
165 | // Len returns the number of items in the cache.
166 | func (c *TwoQueueCache) Len() int {
167 | c.lock.RLock()
168 | defer c.lock.RUnlock()
169 | return c.recent.Len() + c.frequent.Len()
170 | }
171 |
172 | // Keys returns a slice of the keys in the cache.
173 | // The frequently used keys are first in the returned slice.
174 | func (c *TwoQueueCache) Keys() []interface{} {
175 | c.lock.RLock()
176 | defer c.lock.RUnlock()
177 | k1 := c.frequent.Keys()
178 | k2 := c.recent.Keys()
179 | return append(k1, k2...)
180 | }
181 |
182 | // Remove removes the provided key from the cache.
183 | func (c *TwoQueueCache) Remove(key interface{}) {
184 | c.lock.Lock()
185 | defer c.lock.Unlock()
186 | if c.frequent.Remove(key) {
187 | return
188 | }
189 | if c.recent.Remove(key) {
190 | return
191 | }
192 | if c.recentEvict.Remove(key) {
193 | return
194 | }
195 | }
196 |
197 | // Purge is used to completely clear the cache.
198 | func (c *TwoQueueCache) Purge() {
199 | c.lock.Lock()
200 | defer c.lock.Unlock()
201 | c.recent.Purge()
202 | c.frequent.Purge()
203 | c.recentEvict.Purge()
204 | }
205 |
206 | // PurgeOverdue is used to completely clear the overdue cache.
207 | // PurgeOverdue 用于清除过期缓存。
208 | func (c *TwoQueueCache) PurgeOverdue() {
209 | c.lock.Lock()
210 | defer c.lock.Unlock()
211 | c.recent.PurgeOverdue()
212 | c.frequent.PurgeOverdue()
213 | c.recentEvict.PurgeOverdue()
214 | }
215 |
216 | // Contains is used to check if the cache contains a key
217 | // without updating recency or frequency.
218 | func (c *TwoQueueCache) Contains(key interface{}) bool {
219 | c.lock.RLock()
220 | defer c.lock.RUnlock()
221 | return c.frequent.Contains(key) || c.recent.Contains(key)
222 | }
223 |
224 | // Peek is used to inspect the cache value of a key
225 | // without updating recency or frequency.
226 | func (c *TwoQueueCache) Peek(key interface{}) (value interface{}, expirationTime int64, ok bool) {
227 | c.lock.RLock()
228 | defer c.lock.RUnlock()
229 | if val, expirationTime, ok := c.frequent.Peek(key); ok {
230 | return val, expirationTime, ok
231 | }
232 | return c.recent.Peek(key)
233 | }
234 |
--------------------------------------------------------------------------------
/2q_test.go:
--------------------------------------------------------------------------------
1 | package mcache
2 |
3 | import (
4 | "math/rand"
5 | "testing"
6 | )
7 |
8 | func Benchmark2Q_Rand(b *testing.B) {
9 | l, err := New2Q(8192)
10 | if err != nil {
11 | b.Fatalf("err: %v", err)
12 | }
13 |
14 | trace := make([]int64, b.N*2)
15 | for i := 0; i < b.N*2; i++ {
16 | trace[i] = rand.Int63() % 32768
17 | }
18 |
19 | b.ResetTimer()
20 |
21 | var hit, miss int
22 | for i := 0; i < 2*b.N; i++ {
23 | if i%2 == 0 {
24 | l.Add(trace[i], trace[i], 0)
25 | } else {
26 | _, _, ok := l.Get(trace[i])
27 | if ok {
28 | hit++
29 | } else {
30 | miss++
31 | }
32 | }
33 | }
34 | b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss))
35 | }
36 |
37 | func Benchmark2Q_Freq(b *testing.B) {
38 | l, err := New2Q(8192)
39 | if err != nil {
40 | b.Fatalf("err: %v", err)
41 | }
42 |
43 | trace := make([]int64, b.N*2)
44 | for i := 0; i < b.N*2; i++ {
45 | if i%2 == 0 {
46 | trace[i] = rand.Int63() % 16384
47 | } else {
48 | trace[i] = rand.Int63() % 32768
49 | }
50 | }
51 |
52 | b.ResetTimer()
53 |
54 | for i := 0; i < b.N; i++ {
55 | l.Add(trace[i], trace[i], 0)
56 | }
57 | var hit, miss int
58 | for i := 0; i < b.N; i++ {
59 | _, _, ok := l.Get(trace[i])
60 | if ok {
61 | hit++
62 | } else {
63 | miss++
64 | }
65 | }
66 | b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss))
67 | }
68 |
69 | func Test2Q_RandomOps(t *testing.T) {
70 | size := 128
71 | l, err := New2Q(128)
72 | if err != nil {
73 | t.Fatalf("err: %v", err)
74 | }
75 |
76 | n := 200000
77 | for i := 0; i < n; i++ {
78 | key := rand.Int63() % 512
79 | r := rand.Int63()
80 | switch r % 3 {
81 | case 0:
82 | l.Add(key, key, 0)
83 | case 1:
84 | l.Get(key)
85 | case 2:
86 | l.Remove(key)
87 | }
88 |
89 | if l.recent.Len()+l.frequent.Len() > size {
90 | t.Fatalf("bad: recent: %d freq: %d",
91 | l.recent.Len(), l.frequent.Len())
92 | }
93 | }
94 | }
95 |
96 | func Test2Q_Get_RecentToFrequent(t *testing.T) {
97 | l, err := New2Q(128)
98 | if err != nil {
99 | t.Fatalf("err: %v", err)
100 | }
101 |
102 | // Touch all the entries, should be in t1
103 | for i := 0; i < 128; i++ {
104 | l.Add(i, i, 0)
105 | }
106 | if n := l.recent.Len(); n != 128 {
107 | t.Fatalf("bad: %d", n)
108 | }
109 | if n := l.frequent.Len(); n != 0 {
110 | t.Fatalf("bad: %d", n)
111 | }
112 |
113 | // Get should upgrade to t2
114 | for i := 0; i < 128; i++ {
115 | _, _, ok := l.Get(i)
116 | if !ok {
117 | t.Fatalf("missing: %d", i)
118 | }
119 | }
120 | if n := l.recent.Len(); n != 0 {
121 | t.Fatalf("bad: %d", n)
122 | }
123 | if n := l.frequent.Len(); n != 128 {
124 | t.Fatalf("bad: %d", n)
125 | }
126 |
127 | // Get be from t2
128 | for i := 0; i < 128; i++ {
129 | _, _, ok := l.Get(i)
130 | if !ok {
131 | t.Fatalf("missing: %d", i)
132 | }
133 | }
134 | if n := l.recent.Len(); n != 0 {
135 | t.Fatalf("bad: %d", n)
136 | }
137 | if n := l.frequent.Len(); n != 128 {
138 | t.Fatalf("bad: %d", n)
139 | }
140 | }
141 |
142 | func Test2Q_Add_RecentToFrequent(t *testing.T) {
143 | l, err := New2Q(128)
144 | if err != nil {
145 | t.Fatalf("err: %v", err)
146 | }
147 |
148 | // Add initially to recent
149 | l.Add(1, 1, 0)
150 | if n := l.recent.Len(); n != 1 {
151 | t.Fatalf("bad: %d", n)
152 | }
153 | if n := l.frequent.Len(); n != 0 {
154 | t.Fatalf("bad: %d", n)
155 | }
156 |
157 | // Add should upgrade to frequent
158 | l.Add(1, 1, 0)
159 | if n := l.recent.Len(); n != 0 {
160 | t.Fatalf("bad: %d", n)
161 | }
162 | if n := l.frequent.Len(); n != 1 {
163 | t.Fatalf("bad: %d", n)
164 | }
165 |
166 | // Add should remain in frequent
167 | l.Add(1, 1, 0)
168 | if n := l.recent.Len(); n != 0 {
169 | t.Fatalf("bad: %d", n)
170 | }
171 | if n := l.frequent.Len(); n != 1 {
172 | t.Fatalf("bad: %d", n)
173 | }
174 | }
175 |
176 | func Test2Q_Add_RecentEvict(t *testing.T) {
177 | l, err := New2Q(4)
178 | if err != nil {
179 | t.Fatalf("err: %v", err)
180 | }
181 |
182 | // Add 1,2,3,4,5 -> Evict 1
183 | l.Add(1, 1, 0)
184 | l.Add(2, 2, 0)
185 | l.Add(3, 3, 0)
186 | l.Add(4, 4, 0)
187 | l.Add(5, 5, 0)
188 | if n := l.recent.Len(); n != 4 {
189 | t.Fatalf("bad: %d", n)
190 | }
191 | if n := l.recentEvict.Len(); n != 1 {
192 | t.Fatalf("bad: %d", n)
193 | }
194 | if n := l.frequent.Len(); n != 0 {
195 | t.Fatalf("bad: %d", n)
196 | }
197 |
198 | // Pull in the recently evicted
199 | l.Add(1, 1, 0)
200 | if n := l.recent.Len(); n != 3 {
201 | t.Fatalf("bad: %d", n)
202 | }
203 | if n := l.recentEvict.Len(); n != 1 {
204 | t.Fatalf("bad: %d", n)
205 | }
206 | if n := l.frequent.Len(); n != 1 {
207 | t.Fatalf("bad: %d", n)
208 | }
209 |
210 | // Add 6, should cause another recent evict
211 | l.Add(6, 6, 0)
212 | if n := l.recent.Len(); n != 3 {
213 | t.Fatalf("bad: %d", n)
214 | }
215 | if n := l.recentEvict.Len(); n != 2 {
216 | t.Fatalf("bad: %d", n)
217 | }
218 | if n := l.frequent.Len(); n != 1 {
219 | t.Fatalf("bad: %d", n)
220 | }
221 | }
222 |
223 | func Test2Q(t *testing.T) {
224 | l, err := New2Q(128)
225 | if err != nil {
226 | t.Fatalf("err: %v", err)
227 | }
228 |
229 | for i := 0; i < 256; i++ {
230 | l.Add(i, i, 0)
231 | }
232 | if l.Len() != 128 {
233 | t.Fatalf("bad len: %v", l.Len())
234 | }
235 |
236 | for i, k := range l.Keys() {
237 | if v, _, ok := l.Get(k); !ok || v != k || v != i+128 {
238 | t.Fatalf("bad key: %v", k)
239 | }
240 | }
241 | for i := 0; i < 128; i++ {
242 | _, _, ok := l.Get(i)
243 | if ok {
244 | t.Fatalf("should be evicted")
245 | }
246 | }
247 | for i := 128; i < 256; i++ {
248 | _, _, ok := l.Get(i)
249 | if !ok {
250 | t.Fatalf("should not be evicted")
251 | }
252 | }
253 | for i := 128; i < 192; i++ {
254 | l.Remove(i)
255 | _, _, ok := l.Get(i)
256 | if ok {
257 | t.Fatalf("should be deleted")
258 | }
259 | }
260 |
261 | l.Purge()
262 | if l.Len() != 0 {
263 | t.Fatalf("bad len: %v", l.Len())
264 | }
265 | if _, _, ok := l.Get(200); ok {
266 | t.Fatalf("should contain nothing")
267 | }
268 | }
269 |
270 | // Test that Contains doesn't update recent-ness
271 | func Test2Q_Contains(t *testing.T) {
272 | l, err := New2Q(2)
273 | if err != nil {
274 | t.Fatalf("err: %v", err)
275 | }
276 |
277 | l.Add(1, 1, 0)
278 | l.Add(2, 2, 0)
279 | if !l.Contains(1) {
280 | t.Errorf("1 should be contained")
281 | }
282 |
283 | l.Add(3, 3, 0)
284 | if l.Contains(1) {
285 | t.Errorf("Contains should not have updated recent-ness of 1")
286 | }
287 | }
288 |
289 | // Test that Peek doesn't update recent-ness
290 | func Test2Q_Peek(t *testing.T) {
291 | l, err := New2Q(2)
292 | if err != nil {
293 | t.Fatalf("err: %v", err)
294 | }
295 |
296 | l.Add(1, 1, 0)
297 | l.Add(2, 2, 0)
298 | if v, _, ok := l.Peek(1); !ok || v != 1 {
299 | t.Errorf("1 should be set to 1: %v, %v", v, ok)
300 | }
301 |
302 | l.Add(3, 3, 0)
303 | if l.Contains(1) {
304 | t.Errorf("should not have updated recent-ness of 1")
305 | }
306 | }
307 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 songangweb
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 欢迎使用 mcache 内存缓存包
2 |
3 | ### mcache是一个基于golang-lru开发的缓存包
4 |
5 | mcache 增加了缓存过期时间,增加lfu算法,修改了原有arc算法的依赖结构.
6 | 后续还会源源不断增加内存算法.
7 |
8 | ## 特征
9 | 根据过期时间懒汉式删除过期数据,也可主动刷新过期缓存
10 |
11 | ## why? 为什么要用mcache?
12 | 因缓存的使用相关需求,牺牲一部分服务器内存,因减少了网络数据交互,直接使用本机内存,可换取比redis,memcache等更快的缓存速度,
13 | 可做为更高一层的缓存需要
14 |
15 | ## what? 用mcache能做什么?
16 | 可作为超高频率数据使用的缓存存储机制
17 |
18 | ## how? mcache怎么用?
19 | 根据需要的不同缓存淘汰算法,使用对应的调用 方式
20 |
21 |
22 | ## 现已支持内存算法:
23 | ### lru
24 | ### lfu
25 | ### arc
26 | ### 2q
27 | ### hashlru
28 | ### hashlfu
29 |
30 | ## 性能对比
31 | hashlru 与 lru 性能对比
32 |
33 | 算法 | 耗时
34 | ------------- | :-------------
35 | lru | 220.2s
36 | hashlru-2分区 | 267.75s
37 | hashlru-4分区 | 137.36s
38 | hashlru-8分区 | 22.4s
39 | hashlru-16分区 | 23.57s
40 | hashlru-32分区 | 16.84s
41 | hashlru-64分区 | 15.29s
42 |
43 | hashlfu 与 lfu 性能对比
44 |
45 | 算法 | 耗时
46 | ------------- | :-------------
47 | lru | 220.92s
48 | hashlfu-2分区 | 231.28s
49 | hashlfu-4分区 | 72.74s
50 | hashlfu-8分区 | 20.33s
51 | hashlfu-16分区 | 17.76s
52 | hashlfu-32分区 | 16.93s
53 | hashlfu-64分区 | 16.03s
54 |
55 | ### hash算法减少耗时原因:
56 | LruCache在高QPS下的耗时增加原因分析:
57 |
58 | 线程安全的LruCache中有锁的存在。每次读写操作之前都有加锁操作,完成读写操作之后还有解锁操作。 在低QPS下,锁竞争的耗时基本可以忽略;但是在高QPS下,大量的时间消耗在了等待锁的操作上,导致耗时增长。
59 |
60 | HashLruCache适应高QPS场景:
61 |
62 | 针对大量的同步等待操作导致耗时增加的情况,解决方案就是尽量减小临界区。引入Hash机制,对全量数据做分片处理,在原有LruCache的基础上形成HashLruCache,以降低查询耗时。
63 |
64 | HashLruCache引入哈希算法,将缓存数据分散到N个LruCache上。查询时也按照相同的哈希算法,先获取数据可能存在的分片,然后再去对应的分片上查询数据。这样可以增加LruCache的读写操作的并行度,减小同步等待的耗时。
65 |
66 | ## 代码实现:
67 |
68 | len := 10
69 |
70 | // NewLRU 构造一个给定大小的LRU缓存列表
71 | Cache, _ := m_cache.NewLRU(Len)
72 |
73 | // Add 向缓存添加一个值。如果已经存在,则更新信息
74 | Cache.Add(1,1,1614306658000)
75 | Cache.Add(2,2,0) // expirationTime 传0代表无过期时间
76 |
77 | // Get 从缓存中查找一个键的值
78 | Cache.Get(2)
79 |
80 | 更多方法,请查看 interface
81 |
82 |
83 | ## 交流
84 | #### 如果文档中未能覆盖的任何疑问,欢迎您发送邮件到,我会尽快答复。
85 | #### 您可以在提出使用中需要改进的地方,我会考虑合理性并尽快修改。
86 | #### 如果您发现 bug 请及时提 issue,我会尽快确认并修改。
87 | #### 有劳点一下 star,一个小小的 star 是作者回答问题的动力 🤝
88 | ####
89 |
90 |
--------------------------------------------------------------------------------
/arc.go:
--------------------------------------------------------------------------------
1 | package mcache
2 |
3 | import (
4 | "github.com/songangweb/mcache/simplelfu"
5 | "github.com/songangweb/mcache/simplelru"
6 | "sync"
7 | )
8 |
9 | // ARCCache is a thread-safe fixed size Adaptive Replacement LfuCache (ARC).
10 | // ARC is an enhancement over the standard LRU cache in that tracks both
11 | // frequency and recency of use. This avoids a burst in access to new
12 | // entries from evicting the frequently used older entries. It adds some
13 | // additional tracking overhead to a standard LRU cache, computationally
14 | // it is roughly 2x the cost, and the extra memory overhead is linear
15 | // with the size of the cache. ARC has been patented by IBM, but is
16 | // similar to the TwoQueueCache (2Q) which requires setting parameters.
17 | // ARCCache 是一个线程安全的固定大小自适应替换缓存(ARC)。
18 | // ARC是对标准LRU缓存的一个增强,它可以同时跟踪这两个缓存
19 | // 使用的频率和频率。这避免了访问新内容的突然爆发
20 | // 删除常用的旧条目。它增加了一些
21 | // 额外的跟踪开销到一个标准的LRU缓存,计算
22 | // 大约是开销的2倍,额外的内存开销是线性的
23 | // 使用缓存的大小。ARC已经被IBM申请了专利,但它是
24 | // 类似于TwoQueueCache (2Q),需要设置参数。
25 | type ARCCache struct {
26 | // Size为缓存的总容量
27 | size int
28 | // P是对T1或T2的动态偏好
29 | p int
30 |
31 | t1 simplelru.LRUCache // T1 is the LRU for recently accessed items
32 | b1 simplelru.LRUCache // B1 is the LRU for evictions from t1
33 |
34 | t2 simplelfu.LFUCache // T2 is the LFU for frequently accessed items
35 | b2 simplelfu.LFUCache // B2 is the LFU for evictions from t2
36 |
37 | lock sync.RWMutex
38 | }
39 |
40 | // NewARC creates an ARC of the given size
41 | func NewARC(size int) (*ARCCache, error) {
42 | // Create the sub LRUs
43 | t1, err := simplelru.NewLRU(size, nil)
44 | if err != nil {
45 | return nil, err
46 | }
47 | b1, err := simplelru.NewLRU(size, nil)
48 | if err != nil {
49 | return nil, err
50 | }
51 | t2, err := simplelfu.NewLFU(size, nil)
52 | if err != nil {
53 | return nil, err
54 | }
55 | b2, err := simplelfu.NewLFU(size, nil)
56 | if err != nil {
57 | return nil, err
58 | }
59 |
60 | // Initialize the ARC
61 | c := &ARCCache{
62 | size: size,
63 | p: 0,
64 | t1: t1,
65 | b1: b1,
66 | t2: t2,
67 | b2: b2,
68 | }
69 | return c, nil
70 | }
71 |
72 | // Get looks up a key's value from the cache.
73 | // Get 从缓存中查找一个键的值。
74 | func (c *ARCCache) Get(key interface{}) (value interface{}, expirationTime int64, ok bool) {
75 | c.lock.Lock()
76 | defer c.lock.Unlock()
77 |
78 | // If the value is contained in T1 (recent), then
79 | // promote it to T2 (frequent)
80 | if val, expirationTime, ok := c.t1.Peek(key); ok {
81 | c.t1.Remove(key)
82 | c.t2.Add(key, val, expirationTime)
83 | return val, expirationTime, ok
84 | }
85 |
86 | // Check if the value is contained in T2 (frequent)
87 | if val, expirationTime, ok := c.t2.Get(key); ok {
88 | return val, expirationTime, ok
89 | }
90 |
91 | // No hit
92 | return nil, expirationTime, false
93 | }
94 |
95 | // Add adds a value to the cache.
96 | // Add 向缓存添加一个值。如果已经存在,则更新信息
97 | func (c *ARCCache) Add(key, value interface{}, expirationTime int64) {
98 | c.lock.Lock()
99 | defer c.lock.Unlock()
100 |
101 | // Check if the value is contained in T1 (recent), and potentially
102 | // promote it to frequent T2
103 | if c.t1.Contains(key) {
104 | c.t1.Remove(key)
105 | c.t2.Add(key, value, expirationTime)
106 | return
107 | }
108 |
109 | // Check if the value is already in T2 (frequent) and update it
110 | if c.t2.Contains(key) {
111 | c.t2.Add(key, value, expirationTime)
112 | return
113 | }
114 |
115 | // Check if this value was recently evicted as part of the
116 | // recently used list
117 | if c.b1.Contains(key) {
118 | // T1 set is too small, increase P appropriately
119 | delta := 1
120 | b1Len := c.b1.Len()
121 | b2Len := c.b2.Len()
122 | if b2Len > b1Len {
123 | delta = b2Len / b1Len
124 | }
125 | if c.p+delta >= c.size {
126 | c.p = c.size
127 | } else {
128 | c.p += delta
129 | }
130 |
131 | // Potentially need to make room in the cache
132 | if c.t1.Len()+c.t2.Len() >= c.size {
133 | c.replace(false)
134 | }
135 |
136 | // Remove from B1
137 | c.b1.Remove(key)
138 |
139 | // Add the key to the frequently used list
140 | c.t2.Add(key, value, expirationTime)
141 | return
142 | }
143 |
144 | // Check if this value was recently evicted as part of the
145 | // frequently used list
146 | if c.b2.Contains(key) {
147 | // T2 set is too small, decrease P appropriately
148 | delta := 1
149 | b1Len := c.b1.Len()
150 | b2Len := c.b2.Len()
151 | if b1Len > b2Len {
152 | delta = b1Len / b2Len
153 | }
154 | if delta >= c.p {
155 | c.p = 0
156 | } else {
157 | c.p -= delta
158 | }
159 |
160 | // Potentially need to make room in the cache
161 | if c.t1.Len()+c.t2.Len() >= c.size {
162 | c.replace(true)
163 | }
164 |
165 | // Remove from B2
166 | c.b2.Remove(key)
167 |
168 | // Add the key to the frequently used list
169 | c.t2.Add(key, value, expirationTime)
170 | return
171 | }
172 |
173 | // Potentially need to make room in the cache
174 | if c.t1.Len()+c.t2.Len() >= c.size {
175 | c.replace(false)
176 | }
177 |
178 | // Keep the size of the ghost buffers trim
179 | if c.b1.Len() > c.size-c.p {
180 | c.b1.RemoveOldest()
181 | }
182 | if c.b2.Len() > c.p {
183 | c.b2.RemoveOldest()
184 | }
185 |
186 | // Add to the recently seen list
187 | c.t1.Add(key, value, expirationTime)
188 | return
189 | }
190 |
191 | // replace is used to adaptively evict from either T1 or T2
192 | // based on the current learned value of P
193 | // replace 用于自适应地从T1或T2中驱逐,根据P的当前学习值
194 | func (c *ARCCache) replace(b2ContainsKey bool) {
195 | t1Len := c.t1.Len()
196 | if t1Len > 0 && (t1Len > c.p || (t1Len == c.p && b2ContainsKey)) {
197 | k, _, expirationTime, ok := c.t1.RemoveOldest()
198 | if ok {
199 | c.b1.Add(k, nil, expirationTime)
200 | }
201 | } else {
202 | k, _, expirationTime, ok := c.t2.RemoveOldest()
203 | if ok {
204 | c.b2.Add(k, nil, expirationTime)
205 | }
206 | }
207 | }
208 |
209 | // Len returns the number of cached entries
210 | // Len 获取缓存已存在的缓存条数
211 | func (c *ARCCache) Len() int {
212 | c.lock.RLock()
213 | defer c.lock.RUnlock()
214 | return c.t1.Len() + c.t2.Len()
215 | }
216 |
217 | // Keys returns all the cached keys
218 | // Keys 返回缓存中键的切片,从最老到最新
219 | func (c *ARCCache) Keys() []interface{} {
220 | c.lock.RLock()
221 | defer c.lock.RUnlock()
222 | k1 := c.t1.Keys()
223 | k2 := c.t2.Keys()
224 | return append(k1, k2...)
225 | }
226 |
227 | // Remove is used to purge a key from the cache
228 | // Remove 从缓存中移除提供的键。
229 | func (c *ARCCache) Remove(key interface{}) {
230 | c.lock.Lock()
231 | defer c.lock.Unlock()
232 | if c.t1.Remove(key) {
233 | return
234 | }
235 | if c.t2.Remove(key) {
236 | return
237 | }
238 | if c.b1.Remove(key) {
239 | return
240 | }
241 | if c.b2.Remove(key) {
242 | return
243 | }
244 | }
245 |
246 | // Purge is used to clear the cache
247 | // Purge 清除所有缓存项
248 | func (c *ARCCache) Purge() {
249 | c.lock.Lock()
250 | defer c.lock.Unlock()
251 | c.t1.Purge()
252 | c.t2.Purge()
253 | c.b1.Purge()
254 | c.b2.Purge()
255 | }
256 |
257 | // Contains is used to check if the cache contains a key
258 | // without updating recency or frequency.
259 | // Contains 检查某个键是否在缓存中,但不更新缓存的状态
260 | func (c *ARCCache) Contains(key interface{}) bool {
261 | c.lock.RLock()
262 | defer c.lock.RUnlock()
263 | return c.t1.Contains(key) || c.t2.Contains(key)
264 | }
265 |
266 | // ResizeWeight 改变缓存中lfu的Weight大小。
267 | // ResizeWeight 改变缓存中lfu的Weight大小。
268 | func (c *ARCCache) ResizeWeight(percentage int) {
269 | c.lock.Lock()
270 | c.t2.ResizeWeight(percentage)
271 | c.b2.ResizeWeight(percentage)
272 | c.lock.Unlock()
273 | }
274 |
275 | // Peek is used to inspect the cache value of a key
276 | // without updating recency or frequency.
277 | // Peek 在不更新的情况下返回键值(如果没有找到则返回false),不更新缓存的状态
278 | func (c *ARCCache) Peek(key interface{}) (value interface{}, expirationTime int64, ok bool) {
279 | c.lock.RLock()
280 | defer c.lock.RUnlock()
281 | if val, expirationTime, ok := c.t1.Peek(key); ok {
282 | return val, expirationTime, ok
283 | }
284 | return c.t2.Peek(key)
285 | }
286 |
--------------------------------------------------------------------------------
/arc_test.go:
--------------------------------------------------------------------------------
1 | package mcache
2 |
3 | import (
4 | "math/rand"
5 | "testing"
6 | "time"
7 | )
8 |
9 | func init() {
10 | rand.Seed(time.Now().Unix())
11 | }
12 |
13 | func BenchmarkARC_Rand(b *testing.B) {
14 | l, err := NewARC(8192)
15 | if err != nil {
16 | b.Fatalf("err: %v", err)
17 | }
18 |
19 | trace := make([]int64, b.N*2)
20 | for i := 0; i < b.N*2; i++ {
21 | trace[i] = rand.Int63() % 32768
22 | }
23 |
24 | b.ResetTimer()
25 |
26 | var hit, miss int
27 | for i := 0; i < 2*b.N; i++ {
28 | if i%2 == 0 {
29 | l.Add(trace[i], trace[i], 0)
30 | } else {
31 | _, _, ok := l.Get(trace[i])
32 | if ok {
33 | hit++
34 | } else {
35 | miss++
36 | }
37 | }
38 | }
39 | b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss))
40 | }
41 |
42 | func BenchmarkARC_Freq(b *testing.B) {
43 | l, err := NewARC(8192)
44 | if err != nil {
45 | b.Fatalf("err: %v", err)
46 | }
47 |
48 | trace := make([]int64, b.N*2)
49 | for i := 0; i < b.N*2; i++ {
50 | if i%2 == 0 {
51 | trace[i] = rand.Int63() % 16384
52 | } else {
53 | trace[i] = rand.Int63() % 32768
54 | }
55 | }
56 |
57 | b.ResetTimer()
58 |
59 | for i := 0; i < b.N; i++ {
60 | l.Add(trace[i], trace[i], 0)
61 | }
62 | var hit, miss int
63 | for i := 0; i < b.N; i++ {
64 | _, _, ok := l.Get(trace[i])
65 | if ok {
66 | hit++
67 | } else {
68 | miss++
69 | }
70 | }
71 | b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss))
72 | }
73 |
74 | func TestARC_RandomOps(t *testing.T) {
75 | size := 128
76 | l, err := NewARC(128)
77 | if err != nil {
78 | t.Fatalf("err: %v", err)
79 | }
80 |
81 | n := 200000
82 | for i := 0; i < n; i++ {
83 | key := rand.Int63() % 512
84 | r := rand.Int63()
85 | switch r % 3 {
86 | case 0:
87 | l.Add(key, key, 0)
88 | case 1:
89 | l.Get(key)
90 | case 2:
91 | l.Remove(key)
92 | }
93 |
94 | if l.t1.Len()+l.t2.Len() > size {
95 | t.Fatalf("bad: t1: %d t2: %d b1: %d b2: %d p: %d",
96 | l.t1.Len(), l.t2.Len(), l.b1.Len(), l.b2.Len(), l.p)
97 | }
98 | if l.b1.Len()+l.b2.Len() > size {
99 | t.Fatalf("bad: t1: %d t2: %d b1: %d b2: %d p: %d",
100 | l.t1.Len(), l.t2.Len(), l.b1.Len(), l.b2.Len(), l.p)
101 | }
102 | }
103 | }
104 |
105 | func TestARC_Get_RecentToFrequent(t *testing.T) {
106 | l, err := NewARC(128)
107 | if err != nil {
108 | t.Fatalf("err: %v", err)
109 | }
110 |
111 | // Touch all the entries, should be in t1
112 | for i := 0; i < 128; i++ {
113 | l.Add(i, i, 0)
114 | }
115 | if n := l.t1.Len(); n != 128 {
116 | t.Fatalf("bad: %d", n)
117 | }
118 | if n := l.t2.Len(); n != 0 {
119 | t.Fatalf("bad: %d", n)
120 | }
121 |
122 | // Get should upgrade to t2
123 | for i := 0; i < 128; i++ {
124 | _, _, ok := l.Get(i)
125 | if !ok {
126 | t.Fatalf("missing: %d", i)
127 | }
128 | }
129 | if n := l.t1.Len(); n != 0 {
130 | t.Fatalf("bad: %d", n)
131 | }
132 | if n := l.t2.Len(); n != 128 {
133 | t.Fatalf("bad: %d", n)
134 | }
135 |
136 | // Get be from t2
137 | for i := 0; i < 128; i++ {
138 | _, _, ok := l.Get(i)
139 | if !ok {
140 | t.Fatalf("missing: %d", i)
141 | }
142 | }
143 | if n := l.t1.Len(); n != 0 {
144 | t.Fatalf("bad: %d", n)
145 | }
146 | if n := l.t2.Len(); n != 128 {
147 | t.Fatalf("bad: %d", n)
148 | }
149 | }
150 |
151 | func TestARC_Add_RecentToFrequent(t *testing.T) {
152 | l, err := NewARC(128)
153 | if err != nil {
154 | t.Fatalf("err: %v", err)
155 | }
156 |
157 | // Add initially to t1
158 | l.Add(1, 1, 0)
159 | if n := l.t1.Len(); n != 1 {
160 | t.Fatalf("bad: %d", n)
161 | }
162 | if n := l.t2.Len(); n != 0 {
163 | t.Fatalf("bad: %d", n)
164 | }
165 |
166 | // Add should upgrade to t2
167 | l.Add(1, 1, 0)
168 | if n := l.t1.Len(); n != 0 {
169 | t.Fatalf("bad: %d", n)
170 | }
171 | if n := l.t2.Len(); n != 1 {
172 | t.Fatalf("bad: %d", n)
173 | }
174 |
175 | // Add should remain in t2
176 | l.Add(1, 1, 0)
177 | if n := l.t1.Len(); n != 0 {
178 | t.Fatalf("bad: %d", n)
179 | }
180 | if n := l.t2.Len(); n != 1 {
181 | t.Fatalf("bad: %d", n)
182 | }
183 | }
184 |
185 | func TestARC_Adaptive(t *testing.T) {
186 | l, err := NewARC(4)
187 | if err != nil {
188 | t.Fatalf("err: %v", err)
189 | }
190 |
191 | // Fill t1
192 | for i := 0; i < 4; i++ {
193 | l.Add(i, i, 0)
194 | }
195 | if n := l.t1.Len(); n != 4 {
196 | t.Fatalf("bad: %d", n)
197 | }
198 |
199 | // Move to t2
200 | l.Get(0)
201 | l.Get(1)
202 | if n := l.t2.Len(); n != 2 {
203 | t.Fatalf("bad: %d", n)
204 | }
205 |
206 | // Evict from t1
207 | l.Add(4, 4, 0)
208 | if n := l.b1.Len(); n != 1 {
209 | t.Fatalf("bad: %d", n)
210 | }
211 |
212 | //fmt.Println("l.t1: ", l.t1)
213 | //fmt.Println("l.t2: ", l.t2)
214 | //fmt.Println("l.b1: ", l.b1)
215 | //fmt.Println("l.b2: ", l.b2)
216 |
217 | // Current state
218 | // t1 : (MRU) [3, 4] (LRU)
219 | // t2 : (MRU) [0, 1] (LFU)
220 | // b1 : (MRU) [2] (LRU)
221 | // b2 : (MRU) [] (LFU)
222 |
223 | // Add 2, should cause hit on b1
224 | l.Add(2, 2, 0)
225 | if n := l.b1.Len(); n != 1 {
226 | t.Fatalf("bad: %d", n)
227 | }
228 | if l.p != 1 {
229 | t.Fatalf("bad: %d", l.p)
230 | }
231 | if n := l.t2.Len(); n != 3 {
232 | t.Fatalf("bad: %d", n)
233 | }
234 |
235 | //fmt.Println("--------------")
236 | //fmt.Println("l.t1: ", l.t1)
237 | //fmt.Println("l.t2: ", l.t2)
238 | //fmt.Println("l.b1: ", l.b1)
239 | //fmt.Println("l.b2: ", l.b2)
240 |
241 | // Current state
242 | // t1 : (MRU) [4] (LRU)
243 | // t2 : (MRU) [0, 1, 2] (LFU)
244 | // b1 : (MRU) [3] (LRU)
245 | // b2 : (MRU) [] (LFU)
246 |
247 | // Add 4, should migrate to t2
248 | l.Add(4, 4, 0)
249 | if n := l.t1.Len(); n != 0 {
250 | t.Fatalf("bad: %d", n)
251 | }
252 | if n := l.t2.Len(); n != 4 {
253 | t.Fatalf("bad: %d", n)
254 | }
255 |
256 | //fmt.Println("--------------")
257 | //fmt.Println("l.t1: ", l.t1)
258 | //fmt.Println("l.t2: ", l.t2)
259 | //fmt.Println("l.b1: ", l.b1)
260 | //fmt.Println("l.b2: ", l.b2)
261 |
262 | // Current state
263 | // t1 : (MRU) [] (LRU)
264 | // t2 : (MRU) [0, 1, 2, 4] (LFU)
265 | // b1 : (MRU) [3] (LRU)
266 | // b2 : (MRU) [] (LFU)
267 |
268 | // Add 4, should evict to b2
269 | l.Add(5, 5, 0)
270 | if n := l.t1.Len(); n != 1 {
271 | t.Fatalf("bad: %d", n)
272 | }
273 | if n := l.t2.Len(); n != 3 {
274 | t.Fatalf("bad: %d", n)
275 | }
276 | if n := l.b2.Len(); n != 1 {
277 | t.Fatalf("bad: %d", n)
278 | }
279 |
280 | //fmt.Println("--------------")
281 | //fmt.Println("l.t1: ", l.t1)
282 | //fmt.Println("l.t2: ", l.t2)
283 | //fmt.Println("l.b1: ", l.b1)
284 | //fmt.Println("l.b2: ", l.b2)
285 |
286 | // Current state
287 | // t1 : (MRU) [5] (LRU)
288 | // t2 : (MRU) [0, 1, 2] (LFU)
289 | // b1 : (MRU) [3] (LRU)
290 | // b2 : (MRU) [4] (LFU)
291 |
292 | // Add 0, should decrease p
293 | l.Add(0, 0, 0)
294 | if n := l.t1.Len(); n != 1 {
295 | t.Fatalf("bad: %d", n)
296 | }
297 | if n := l.t2.Len(); n != 3 {
298 | t.Fatalf("bad: %d", n)
299 | }
300 | if n := l.b1.Len(); n != 1 {
301 | t.Fatalf("bad: %d", n)
302 | }
303 | if n := l.b2.Len(); n != 1 {
304 | t.Fatalf("bad: %d", n)
305 | }
306 | if l.p != 1 {
307 | t.Fatalf("bad: %d", l.p)
308 | }
309 |
310 | //fmt.Println("--------------")
311 | //fmt.Println("l.t1: ", l.t1)
312 | //fmt.Println("l.t2: ", l.t2)
313 | //fmt.Println("l.b1: ", l.b1)
314 | //fmt.Println("l.b2: ", l.b2)
315 |
316 | // Current state
317 | // t1 : (MRU) [5] (LRU)
318 | // t2 : (MRU) [0, 1, 2] (LFU)
319 | // b1 : (MRU) [3] (LRU)
320 | // b2 : (MRU) [4] (LFU)
321 | }
322 |
323 | func TestARC(t *testing.T) {
324 | l, err := NewARC(128)
325 | if err != nil {
326 | t.Fatalf("err: %v", err)
327 | }
328 |
329 | for i := 0; i < 256; i++ {
330 | l.Add(i, i, 0)
331 | }
332 | if l.Len() != 128 {
333 | t.Fatalf("bad len: %v", l.Len())
334 | }
335 |
336 | for i, k := range l.Keys() {
337 | if v, _, ok := l.Get(k); !ok || v != k || v != i+128 {
338 | t.Fatalf("bad key: %v", k)
339 | }
340 | }
341 | for i := 0; i < 128; i++ {
342 | _, _, ok := l.Get(i)
343 | if ok {
344 | t.Fatalf("should be evicted")
345 | }
346 | }
347 | for i := 128; i < 256; i++ {
348 | _, _, ok := l.Get(i)
349 | if !ok {
350 | t.Fatalf("should not be evicted")
351 | }
352 | }
353 | for i := 128; i < 192; i++ {
354 | l.Remove(i)
355 | _, _, ok := l.Get(i)
356 | if ok {
357 | t.Fatalf("should be deleted")
358 | }
359 | }
360 |
361 | l.Purge()
362 | if l.Len() != 0 {
363 | t.Fatalf("bad len: %v", l.Len())
364 | }
365 | if _, _, ok := l.Get(200); ok {
366 | t.Fatalf("should contain nothing")
367 | }
368 | }
369 |
370 | // Test that Contains doesn't update recent-ness
371 | func TestARC_Contains(t *testing.T) {
372 | l, err := NewARC(2)
373 | if err != nil {
374 | t.Fatalf("err: %v", err)
375 | }
376 |
377 | l.Add(1, 1, 0)
378 | l.Add(2, 2, 0)
379 | if !l.Contains(1) {
380 | t.Errorf("1 should be contained")
381 | }
382 |
383 | l.Add(3, 3, 0)
384 | if l.Contains(1) {
385 | t.Errorf("Contains should not have updated recent-ness of 1")
386 | }
387 | }
388 |
389 | // Test that Peek doesn't update recent-ness
390 | func TestARC_Peek(t *testing.T) {
391 | l, err := NewARC(2)
392 | if err != nil {
393 | t.Fatalf("err: %v", err)
394 | }
395 |
396 | l.Add(1, 1, 0)
397 | l.Add(2, 2, 0)
398 | if v, _, ok := l.Peek(1); !ok || v != 1 {
399 | t.Errorf("1 should be set to 1: %v, %v", v, ok)
400 | }
401 |
402 | l.Add(3, 3, 0)
403 | if l.Contains(1) {
404 | t.Errorf("should not have updated recent-ness of 1")
405 | }
406 | }
407 |
408 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/songangweb/mcache
2 |
3 | go 1.15
4 |
5 | require github.com/pkg/profile v1.5.0
6 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug=
2 | github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
3 |
--------------------------------------------------------------------------------
/hashLfu.go:
--------------------------------------------------------------------------------
1 | package mcache
2 |
3 | import (
4 | "crypto/md5"
5 | "github.com/songangweb/mcache/simplelfu"
6 | "math"
7 | "runtime"
8 | "sync"
9 | )
10 |
11 | // HashLfuCache is a thread-safe fixed size HashLFU cache.
12 | // HashLfuCache 实现一个给定大小的HashLFU缓存
13 | type HashLfuCache struct {
14 | list []*HashLfuCacheOne
15 | sliceNum int
16 | size int
17 | }
18 |
19 | type HashLfuCacheOne struct {
20 | lfu simplelfu.LFUCache
21 | lock sync.RWMutex
22 | }
23 |
24 | // NewHashLFU creates an LFU of the given size.
25 | // NewHashLFU 构造一个给定大小的LFU
26 | func NewHashLFU(size, sliceNum int) (*HashLfuCache, error) {
27 | return NewHashLfuWithEvict(size, sliceNum, nil)
28 | }
29 |
30 | // NewHashLfuWithEvict constructs a fixed size cache with the given eviction
31 | // callback.
32 | // NewHashLfuWithEvict 用于在缓存条目被淘汰时的回调函数
33 | func NewHashLfuWithEvict(size, sliceNum int, onEvicted func(key interface{}, value interface{}, expirationTime int64)) (*HashLfuCache, error) {
34 | if 0 == sliceNum {
35 | // 设置为当前cpu数量
36 | sliceNum = runtime.NumCPU()
37 | }
38 | if size < sliceNum {
39 | size = sliceNum
40 | }
41 |
42 | // 计算出每个分片的数据长度
43 | lfuLen := int(math.Ceil(float64(size / sliceNum)))
44 | var h HashLfuCache
45 | h.size = size
46 | h.sliceNum = sliceNum
47 | h.list = make([]*HashLfuCacheOne, sliceNum)
48 | for i := 0; i < sliceNum; i++ {
49 | l, _ := simplelfu.NewLFU(lfuLen, onEvicted)
50 | h.list[i] = &HashLfuCacheOne{
51 | lfu: l,
52 | }
53 | }
54 |
55 | return &h, nil
56 | }
57 |
58 | // Purge is used to completely clear the cache.
59 | // Purge 清除所有缓存项
60 | func (h *HashLfuCache) Purge() {
61 | for i := 0; i < h.sliceNum; i++ {
62 | h.list[i].lock.Lock()
63 | h.list[i].lfu.Purge()
64 | h.list[i].lock.Unlock()
65 | }
66 | }
67 |
68 | // PurgeOverdue is used to completely clear the overdue cache.
69 | // PurgeOverdue 用于清除过期缓存。
70 | func (h *HashLfuCache) PurgeOverdue() {
71 | for i := 0; i < h.sliceNum; i++ {
72 | h.list[i].lock.Lock()
73 | h.list[i].lfu.PurgeOverdue()
74 | h.list[i].lock.Unlock()
75 | }
76 | }
77 |
78 | // Add adds a value to the cache. Returns true if an eviction occurred.
79 | // Add 向缓存添加一个值。如果已经存在,则更新信息
80 | func (h *HashLfuCache) Add(key interface{}, value interface{}, expirationTime int64) (evicted bool) {
81 | sliceKey := h.modulus(&key)
82 |
83 | h.list[sliceKey].lock.Lock()
84 | evicted = h.list[sliceKey].lfu.Add(key, value, expirationTime)
85 | h.list[sliceKey].lock.Unlock()
86 | return evicted
87 | }
88 |
89 | // Get looks up a key's value from the cache.
90 | // Get 从缓存中查找一个键的值。
91 | func (h *HashLfuCache) Get(key interface{}) (value interface{}, expirationTime int64, ok bool) {
92 | sliceKey := h.modulus(&key)
93 |
94 | h.list[sliceKey].lock.Lock()
95 | value, expirationTime, ok = h.list[sliceKey].lfu.Get(key)
96 | h.list[sliceKey].lock.Unlock()
97 | return value, expirationTime, ok
98 | }
99 |
100 | // Contains checks if a key is in the cache, without updating the
101 | // recent-ness or deleting it for being stale.
102 | // Contains 检查某个键是否在缓存中,但不更新缓存的状态
103 | func (h *HashLfuCache) Contains(key interface{}) bool {
104 | sliceKey := h.modulus(&key)
105 |
106 | h.list[sliceKey].lock.RLock()
107 | containKey := h.list[sliceKey].lfu.Contains(key)
108 | h.list[sliceKey].lock.RUnlock()
109 | return containKey
110 | }
111 |
112 | // Peek returns the key value (or undefined if not found) without updating
113 | // the "recently used"-ness of the key.
114 | // Peek 在不更新的情况下返回键值(如果没有找到则返回false),不更新缓存的状态
115 | func (h *HashLfuCache) Peek(key interface{}) (value interface{}, expirationTime int64, ok bool) {
116 | sliceKey := h.modulus(&key)
117 |
118 | h.list[sliceKey].lock.RLock()
119 | value, expirationTime, ok = h.list[sliceKey].lfu.Peek(key)
120 | h.list[sliceKey].lock.RUnlock()
121 | return value, expirationTime, ok
122 | }
123 |
124 | // ContainsOrAdd checks if a key is in the cache without updating the
125 | // recent-ness or deleting it for being stale, and if not, adds the value.
126 | // Returns whether found and whether an eviction occurred.
127 | // ContainsOrAdd 检查键是否在缓存中,而不更新
128 | // 最近或删除它,因为它是陈旧的,如果不是,添加值。
129 | // 返回是否找到和是否发生了驱逐。
130 | func (h *HashLfuCache) ContainsOrAdd(key interface{}, value interface{}, expirationTime int64) (ok, evicted bool) {
131 | sliceKey := h.modulus(&key)
132 |
133 | h.list[sliceKey].lock.Lock()
134 | defer h.list[sliceKey].lock.Unlock()
135 |
136 | if h.list[sliceKey].lfu.Contains(key) {
137 | return true, false
138 | }
139 | evicted = h.list[sliceKey].lfu.Add(key, value, expirationTime)
140 | return false, evicted
141 | }
142 |
143 | // PeekOrAdd checks if a key is in the cache without updating the
144 | // recent-ness or deleting it for being stale, and if not, adds the value.
145 | // Returns whether found and whether an eviction occurred.
146 | // PeekOrAdd 如果一个key在缓存中,那么这个key就不会被更新
147 | // 最近或删除它,因为它是陈旧的,如果不是,添加值。
148 | // 返回是否找到和是否发生了驱逐。
149 | func (h *HashLfuCache) PeekOrAdd(key interface{}, value interface{}, expirationTime int64) (previous interface{}, ok, evicted bool) {
150 | sliceKey := h.modulus(&key)
151 |
152 | h.list[sliceKey].lock.Lock()
153 | defer h.list[sliceKey].lock.Unlock()
154 |
155 | previous, expirationTime, ok = h.list[sliceKey].lfu.Peek(key)
156 | if ok {
157 | return previous, true, false
158 | }
159 |
160 | evicted = h.list[sliceKey].lfu.Add(key, value, expirationTime)
161 | return nil, false, evicted
162 | }
163 |
164 | // Remove removes the provided key from the cache.
165 | // Remove 从缓存中移除提供的键。
166 | func (h *HashLfuCache) Remove(key interface{}) (present bool) {
167 | sliceKey := h.modulus(&key)
168 |
169 | h.list[sliceKey].lock.Lock()
170 | present = h.list[sliceKey].lfu.Remove(key)
171 | h.list[sliceKey].lock.Unlock()
172 | return
173 | }
174 |
175 | // Resize changes the cache size.
176 | // Resize 调整缓存大小,返回调整前的数量
177 | func (h *HashLfuCache) Resize(size int) (evicted int) {
178 | if size < h.sliceNum {
179 | size = h.sliceNum
180 | }
181 |
182 | // 计算出每个分片的数据长度
183 | lfuLen := int(math.Ceil(float64(size / h.sliceNum)))
184 |
185 | for i := 0; i < h.sliceNum; i++ {
186 | h.list[i].lock.Lock()
187 | evicted = h.list[i].lfu.Resize(lfuLen)
188 | h.list[i].lock.Unlock()
189 | }
190 | return evicted
191 | }
192 |
193 | // ResizeWeight 改变缓存中Weight大小。
194 | // ResizeWeight 改变缓存中Weight大小。
195 | func (h *HashLfuCache) ResizeWeight(percentage int) {
196 | for i := 0; i < h.sliceNum; i++ {
197 | h.list[i].lock.Lock()
198 | h.list[i].lfu.ResizeWeight(percentage)
199 | h.list[i].lock.Unlock()
200 | }
201 | }
202 |
203 | // Keys returns a slice of the keys in the cache, from oldest to newest.
204 | // Keys 返回缓存的切片,从最老的到最新的。
205 | func (h *HashLfuCache) Keys() []interface{} {
206 |
207 | var keys []interface{}
208 |
209 | allKeys := make([][]interface{}, h.sliceNum)
210 |
211 | // 记录最大的 oneKeys 长度
212 | var oneKeysMaxLen int
213 |
214 | for s := 0; s < h.sliceNum; s++ {
215 | h.list[s].lock.RLock()
216 |
217 | if h.list[s].lfu.Len() > oneKeysMaxLen {
218 | oneKeysMaxLen = h.list[s].lfu.Len()
219 | }
220 |
221 | oneKeys := make([]interface{}, h.list[s].lfu.Len())
222 | oneKeys = h.list[s].lfu.Keys()
223 | h.list[s].lock.RUnlock()
224 |
225 | allKeys[s] = oneKeys
226 | }
227 |
228 | for c := 0; c < len(allKeys); c++ {
229 | for _, v := range allKeys[c] {
230 | keys = append(keys, v)
231 | }
232 | }
233 |
234 | //for i := 0; i < h.list[0].lfu.Len(); i++ {
235 | // for c := 0; c < len(allKeys); c++ {
236 | // if len(allKeys[c]) > i {
237 | // keys = append(keys, allKeys[c][i])
238 | // }
239 | // }
240 | //}
241 |
242 | return keys
243 | }
244 |
245 | // Len returns the number of items in the cache.
246 | // Len 获取缓存已存在的缓存条数
247 | func (h *HashLfuCache) Len() int {
248 | var length = 0
249 |
250 | for i := 0; i < h.sliceNum; i++ {
251 | h.list[i].lock.RLock()
252 | length = length + h.list[i].lfu.Len()
253 | h.list[i].lock.RUnlock()
254 | }
255 | return length
256 | }
257 |
258 | func (h *HashLfuCache) modulus(key *interface{}) int {
259 | str := InterfaceToString(*key)
260 | return int(md5.Sum([]byte(str))[0]) % h.sliceNum
261 | }
262 |
--------------------------------------------------------------------------------
/hashLfu_test.go:
--------------------------------------------------------------------------------
1 | package mcache
2 |
3 | import (
4 | "fmt"
5 | "math/rand"
6 | "runtime"
7 | "strconv"
8 | "sync"
9 | "testing"
10 | )
11 |
12 | func BenchmarkHashLFU_Rand(b *testing.B) {
13 | l, err := NewHashLFU(8192, 0)
14 | if err != nil {
15 | b.Fatalf("err: %v", err)
16 | }
17 |
18 | trace := make([]int64, b.N*2)
19 | for i := 0; i < b.N*2; i++ {
20 | trace[i] = rand.Int63() % 32768
21 | }
22 |
23 | b.ResetTimer()
24 |
25 | var hit, miss int
26 | for i := 0; i < 2*b.N; i++ {
27 | if i%2 == 0 {
28 | l.Add(trace[i], trace[i], 0)
29 | } else {
30 | _, _, ok := l.Get(trace[i])
31 | if ok {
32 | hit++
33 | } else {
34 | miss++
35 | }
36 | }
37 | }
38 | b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss))
39 | }
40 |
41 | func BenchmarkHashLFU_Freq(b *testing.B) {
42 | l, err := NewHashLFU(8192, 0)
43 | if err != nil {
44 | b.Fatalf("err: %v", err)
45 | }
46 |
47 | trace := make([]int64, b.N*2)
48 | for i := 0; i < b.N*2; i++ {
49 | if i%2 == 0 {
50 | trace[i] = rand.Int63() % 16384
51 | } else {
52 | trace[i] = rand.Int63() % 32768
53 | }
54 | }
55 |
56 | b.ResetTimer()
57 |
58 | for i := 0; i < b.N; i++ {
59 | l.Add(trace[i], trace[i], 0)
60 | }
61 | var hit, miss int
62 | for i := 0; i < b.N; i++ {
63 | _, _, ok := l.Get(trace[i])
64 | if ok {
65 | hit++
66 | } else {
67 | miss++
68 | }
69 | }
70 | b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss))
71 | }
72 |
73 | func TestHashLFUKeys(t *testing.T) {
74 | evictCounter := 0
75 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
76 | if k != v {
77 | t.Fatalf("Evict values not equal (%v!=%v)", k, v)
78 | }
79 | evictCounter++
80 | }
81 | l, err := NewHashLfuWithEvict(128, 8, onEvicted)
82 | if err != nil {
83 | t.Fatalf("err: %v", err)
84 | }
85 |
86 | // 每个切片对应的长度
87 | lenMap := map[int]int{
88 | 1: 1,
89 | 2: 2,
90 | 3: 0,
91 | 4: 2,
92 | 5: 3,
93 | }
94 | lenCount := make(map[int]int, 8)
95 |
96 | // 获取总key
97 | var totalCount int
98 | for _, v := range lenMap {
99 | totalCount += v
100 | }
101 |
102 | for i := 0; i < 128; i++ {
103 | var ak interface{} = i
104 | // 获取切片索引
105 | av := l.modulus(&ak)
106 | // 获取对应切片长度
107 | if lMax, ok := lenMap[av]; ok {
108 | // 对应切片计数
109 | curNum := lenCount[av]
110 | if curNum < lMax {
111 | l.Add(ak, i, 0)
112 | }
113 | lenCount[av]++
114 | } else {
115 | continue
116 | }
117 | }
118 |
119 | keyLen := len(l.Keys())
120 | if totalCount != len(l.Keys()) {
121 | t.Fatalf("Evict values not equal (%v!=%v)", totalCount, keyLen)
122 | }
123 |
124 | }
125 |
126 | func TestHashLFU(t *testing.T) {
127 | evictCounter := 0
128 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
129 | if k != v {
130 | t.Fatalf("Evict values not equal (%v!=%v)", k, v)
131 | }
132 | evictCounter++
133 | }
134 | l, err := NewHashLfuWithEvict(128, 0, onEvicted)
135 | if err != nil {
136 | t.Fatalf("err: %v", err)
137 | }
138 |
139 | for i := 0; i < 256; i++ {
140 | l.Add(i, i, 0)
141 | }
142 |
143 | if l.Len() != 128 {
144 | t.Fatalf("bad len: %v", l.Len())
145 | }
146 |
147 | if evictCounter != 128 {
148 | t.Fatalf("bad evict count: %v", evictCounter)
149 | }
150 |
151 | for _, k := range l.Keys() {
152 | if v, _, ok := l.Get(k); !ok || v != k {
153 | t.Fatalf("bad key: %v, val: %v", k, v)
154 | }
155 | }
156 |
157 | for i := 128; i < 192; i++ {
158 | l.Remove(i)
159 | _, _, ok := l.Get(i)
160 | if ok {
161 | t.Fatalf("should be deleted")
162 | }
163 | }
164 |
165 | l.Purge()
166 | if l.Len() != 0 {
167 | t.Fatalf("bad len: %v", l.Len())
168 | }
169 | if _, _, ok := l.Get(200); ok {
170 | t.Fatalf("should contain nothing")
171 | }
172 | }
173 |
174 | // test that Add returns true/false if an eviction occurred
175 | func TestHashLFUAdd(t *testing.T) {
176 | evictCounter := 0
177 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
178 | evictCounter++
179 | }
180 |
181 | l, err := NewLruWithEvict(1, onEvicted)
182 | if err != nil {
183 | t.Fatalf("err: %v", err)
184 | }
185 |
186 | if l.Add(1, 1, 0) == false || evictCounter != 0 {
187 | t.Errorf("should not have an eviction")
188 | }
189 | if l.Add(2, 2, 0) == false || evictCounter != 1 {
190 | t.Errorf("should have an eviction")
191 | }
192 | }
193 |
194 | // test that Contains doesn't update recent-ness
195 | func TestHashLFUContains(t *testing.T) {
196 | l, err := NewLRU(2)
197 | if err != nil {
198 | t.Fatalf("err: %v", err)
199 | }
200 |
201 | l.Add(1, 1, 0)
202 | l.Add(2, 2, 0)
203 | if !l.Contains(1) {
204 | t.Errorf("1 should be contained")
205 | }
206 |
207 | l.Add(3, 3, 0)
208 | if l.Contains(1) {
209 | t.Errorf("Contains should not have updated recent-ness of 1")
210 | }
211 | }
212 |
213 | // test that ContainsOrAdd doesn't update recent-ness
214 | func TestHashLFUContainsOrAdd(t *testing.T) {
215 | l, err := NewLRU(2)
216 | if err != nil {
217 | t.Fatalf("err: %v", err)
218 | }
219 |
220 | l.Add(1, 1, 0)
221 | l.Add(2, 2, 0)
222 | contains, evict := l.ContainsOrAdd(1, 1, 0)
223 | if !contains {
224 | t.Errorf("1 should be contained")
225 | }
226 | if evict {
227 | t.Errorf("nothing should be evicted here")
228 | }
229 |
230 | l.Add(3, 3, 0)
231 | contains, evict = l.ContainsOrAdd(1, 1, 0)
232 | if contains {
233 | t.Errorf("1 should not have been contained")
234 | }
235 | if !evict {
236 | t.Errorf("an eviction should have occurred")
237 | }
238 | if !l.Contains(1) {
239 | t.Errorf("now 1 should be contained")
240 | }
241 | }
242 |
243 | // test that PeekOrAdd doesn't update recent-ness
244 | func TestHashLFUPeekOrAdd(t *testing.T) {
245 | l, err := NewLRU(2)
246 | if err != nil {
247 | t.Fatalf("err: %v", err)
248 | }
249 |
250 | l.Add(1, 1, 0)
251 | l.Add(2, 2, 0)
252 | previous, contains, evict := l.PeekOrAdd(1, 1, 0)
253 | if !contains {
254 | t.Errorf("1 should be contained")
255 | }
256 | if evict {
257 | t.Errorf("nothing should be evicted here")
258 | }
259 | if previous != 1 {
260 | t.Errorf("previous is not equal to 1")
261 | }
262 |
263 | l.Add(3, 3, 0)
264 | contains, evict = l.ContainsOrAdd(1, 1, 0)
265 | if contains {
266 | t.Errorf("1 should not have been contained")
267 | }
268 | if !evict {
269 | t.Errorf("an eviction should have occurred")
270 | }
271 | if !l.Contains(1) {
272 | t.Errorf("now 1 should be contained")
273 | }
274 | }
275 |
276 | // test that Peek doesn't update recent-ness
277 | func TestHashLFUPeek(t *testing.T) {
278 | l, err := NewLRU(2)
279 | if err != nil {
280 | t.Fatalf("err: %v", err)
281 | }
282 |
283 | l.Add(1, 1, 0)
284 | l.Add(2, 2, 0)
285 | if v, _, ok := l.Peek(1); !ok || v != 1 {
286 | t.Errorf("1 should be set to 1: %v, %v", v, ok)
287 | }
288 |
289 | l.Add(3, 3, 0)
290 | if l.Contains(1) {
291 | t.Errorf("should not have updated recent-ness of 1")
292 | }
293 | }
294 |
295 | // test that Resize can upsize and downsize
296 | func TestHashLFUResize(t *testing.T) {
297 | onEvictCounter := 0
298 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
299 | onEvictCounter++
300 | }
301 | l, err := NewLruWithEvict(2, onEvicted)
302 | if err != nil {
303 | t.Fatalf("err: %v", err)
304 | }
305 |
306 | // Downsize
307 | l.Add(1, 1, 0)
308 | l.Add(2, 2, 0)
309 | evicted := l.Resize(1)
310 | if evicted != 1 {
311 | t.Errorf("1 element should have been evicted: %v", evicted)
312 | }
313 | if onEvictCounter != 1 {
314 | t.Errorf("onEvicted should have been called 1 time: %v", onEvictCounter)
315 | }
316 |
317 | l.Add(3, 3, 0)
318 | if l.Contains(1) {
319 | t.Errorf("Element 1 should have been evicted")
320 | }
321 |
322 | // Upsize
323 | evicted = l.Resize(2)
324 | if evicted != 0 {
325 | t.Errorf("0 elements should have been evicted: %v", evicted)
326 | }
327 |
328 | l.Add(4, 4, 0)
329 | if !l.Contains(3) || !l.Contains(4) {
330 | t.Errorf("lruCache should have contained 2 elements")
331 | }
332 | }
333 |
334 | // HashLFU 性能压测
335 | func TestHashLFU_Performance(t *testing.T) {
336 | runtime.GOMAXPROCS(runtime.NumCPU())
337 | fmt.Println("runtime.NumCPU(): ", runtime.NumCPU())
338 | //cpu 性能分析 go tool pprof --pdf cpu ./cpu2.pprof > cpu.pdf
339 | //开始性能分析, 返回一个停止接口
340 | //stopper1 := profile.Start(profile.CPUProfile, profile.ProfilePath("."))
341 | ////在main()结束时停止性能分析
342 | //defer stopper1.Stop()
343 |
344 | //// 查看导致阻塞同步的堆栈跟踪
345 | //stopper2 := profile.Start(profile.BlockProfile, profile.ProfilePath("."))
346 | //// 在main()结束时停止性能分析
347 | //defer stopper2.Stop()
348 | //
349 | //// 查看当前所有运行的 goroutines 堆栈跟踪
350 | //stopper3 := profile.Start(profile.GoroutineProfile, profile.ProfilePath("."))
351 | //// 在main()结束时停止性能分析
352 | //defer stopper3.Stop()
353 | //
354 | //// 查看当前所有运行的 goroutines 堆栈跟踪
355 | //stopper4 := profile.Start(profile.MemProfile, profile.ProfilePath("."))
356 | //// 在main()结束时停止性能分析
357 | //defer stopper4.Stop()
358 |
359 | count := 10000000
360 | l, _ := NewHashLFU(20000, 64)
361 |
362 | wg := &sync.WaitGroup{}
363 | for k := 0; k < count; k++ {
364 | wg.Add(1)
365 | go HashlfuPerformanceOne(l, wg, k)
366 | }
367 | wg.Wait()
368 |
369 | }
370 |
371 | func HashlfuPerformanceOne(h *HashLfuCache, c *sync.WaitGroup, k int) {
372 |
373 | for i := 0; i < 5; i++ {
374 |
375 | var strKey string
376 | strKey = strconv.Itoa(k) + "_" + strconv.Itoa(i)
377 |
378 | h.Add(strKey, &testJsonStr, 0)
379 | }
380 |
381 | // 通知main已经结束循环(我搞定了!)
382 | c.Done()
383 | }
384 |
--------------------------------------------------------------------------------
/hashLru.go:
--------------------------------------------------------------------------------
1 | package mcache
2 |
3 | import (
4 | "crypto/md5"
5 | "github.com/songangweb/mcache/simplelru"
6 | "math"
7 | "runtime"
8 | "sync"
9 | )
10 |
11 | // HashLruCache is a thread-safe fixed size LRU cache.
12 | // HashLruCache 实现一个给定大小的LRU缓存
13 | type HashLruCache struct {
14 | list []*HashLruCacheOne
15 | sliceNum int
16 | size int
17 | }
18 |
19 | type HashLruCacheOne struct {
20 | lru simplelru.LRUCache
21 | lock sync.RWMutex
22 | }
23 |
24 | // NewHashLRU creates an LRU of the given size.
25 | // NewHashLRU 构造一个给定大小的LRU
26 | func NewHashLRU(size, sliceNum int) (*HashLruCache, error) {
27 | return NewHashLruWithEvict(size, sliceNum, nil)
28 | }
29 |
30 | // NewHashLruWithEvict constructs a fixed size cache with the given eviction
31 | // callback.
32 | // NewHashLruWithEvict 用于在缓存条目被淘汰时的回调函数
33 | func NewHashLruWithEvict(size, sliceNum int, onEvicted func(key interface{}, value interface{}, expirationTime int64)) (*HashLruCache, error) {
34 | if 0 == sliceNum {
35 | // 设置为当前cpu数量
36 | sliceNum = runtime.NumCPU()
37 | }
38 | if size < sliceNum {
39 | size = sliceNum
40 | }
41 |
42 | // 计算出每个分片的数据长度
43 | lruLen := int(math.Ceil(float64(size / sliceNum)))
44 | var h HashLruCache
45 | h.size = size
46 | h.sliceNum = sliceNum
47 | h.list = make([]*HashLruCacheOne, sliceNum)
48 | for i := 0; i < sliceNum; i++ {
49 | l, _ := simplelru.NewLRU(lruLen, onEvicted)
50 | h.list[i] = &HashLruCacheOne{
51 | lru: l,
52 | }
53 | }
54 |
55 | return &h, nil
56 | }
57 |
58 | // Purge is used to completely clear the cache.
59 | // Purge 清除所有缓存项
60 | func (h *HashLruCache) Purge() {
61 | for i := 0; i < h.sliceNum; i++ {
62 | h.list[i].lock.Lock()
63 | h.list[i].lru.Purge()
64 | h.list[i].lock.Unlock()
65 | }
66 | }
67 |
68 | // PurgeOverdue is used to completely clear the overdue cache.
69 | // PurgeOverdue 用于清除过期缓存。
70 | func (h *HashLruCache) PurgeOverdue() {
71 | for i := 0; i < h.sliceNum; i++ {
72 | h.list[i].lock.Lock()
73 | h.list[i].lru.PurgeOverdue()
74 | h.list[i].lock.Unlock()
75 | }
76 | }
77 |
78 | // Add adds a value to the cache. Returns true if an eviction occurred.
79 | // Add 向缓存添加一个值。如果已经存在,则更新信息
80 | func (h *HashLruCache) Add(key interface{}, value interface{}, expirationTime int64) (evicted bool) {
81 | sliceKey := h.modulus(&key)
82 |
83 | h.list[sliceKey].lock.Lock()
84 | evicted = h.list[sliceKey].lru.Add(key, value, expirationTime)
85 | h.list[sliceKey].lock.Unlock()
86 | return evicted
87 | }
88 |
89 | // Get looks up a key's value from the cache.
90 | // Get 从缓存中查找一个键的值。
91 | func (h *HashLruCache) Get(key interface{}) (value interface{}, expirationTime int64, ok bool) {
92 | sliceKey := h.modulus(&key)
93 |
94 | h.list[sliceKey].lock.Lock()
95 | value, expirationTime, ok = h.list[sliceKey].lru.Get(key)
96 | h.list[sliceKey].lock.Unlock()
97 | return value, expirationTime, ok
98 | }
99 |
100 | // Contains checks if a key is in the cache, without updating the
101 | // recent-ness or deleting it for being stale.
102 | // Contains 检查某个键是否在缓存中,但不更新缓存的状态
103 | func (h *HashLruCache) Contains(key interface{}) bool {
104 | sliceKey := h.modulus(&key)
105 |
106 | h.list[sliceKey].lock.RLock()
107 | containKey := h.list[sliceKey].lru.Contains(key)
108 | h.list[sliceKey].lock.RUnlock()
109 | return containKey
110 | }
111 |
112 | // Peek returns the key value (or undefined if not found) without updating
113 | // the "recently used"-ness of the key.
114 | // Peek 在不更新的情况下返回键值(如果没有找到则返回false),不更新缓存的状态
115 | func (h *HashLruCache) Peek(key interface{}) (value interface{}, expirationTime int64, ok bool) {
116 | sliceKey := h.modulus(&key)
117 |
118 | h.list[sliceKey].lock.RLock()
119 | value, expirationTime, ok = h.list[sliceKey].lru.Peek(key)
120 | h.list[sliceKey].lock.RUnlock()
121 | return value, expirationTime, ok
122 | }
123 |
124 | // ContainsOrAdd checks if a key is in the cache without updating the
125 | // recent-ness or deleting it for being stale, and if not, adds the value.
126 | // Returns whether found and whether an eviction occurred.
127 | // ContainsOrAdd 检查键是否在缓存中,而不更新
128 | // 最近或删除它,因为它是陈旧的,如果不是,添加值。
129 | // 返回是否找到和是否发生了驱逐。
130 | func (h *HashLruCache) ContainsOrAdd(key interface{}, value interface{}, expirationTime int64) (ok, evicted bool) {
131 | sliceKey := h.modulus(&key)
132 |
133 | h.list[sliceKey].lock.Lock()
134 | defer h.list[sliceKey].lock.Unlock()
135 |
136 | if h.list[sliceKey].lru.Contains(key) {
137 | return true, false
138 | }
139 | evicted = h.list[sliceKey].lru.Add(key, value, expirationTime)
140 | return false, evicted
141 | }
142 |
143 | // PeekOrAdd checks if a key is in the cache without updating the
144 | // recent-ness or deleting it for being stale, and if not, adds the value.
145 | // Returns whether found and whether an eviction occurred.
146 | // PeekOrAdd 如果一个key在缓存中,那么这个key就不会被更新
147 | // 最近或删除它,因为它是陈旧的,如果不是,添加值。
148 | // 返回是否找到和是否发生了驱逐。
149 | func (h *HashLruCache) PeekOrAdd(key interface{}, value interface{}, expirationTime int64) (previous interface{}, ok, evicted bool) {
150 | sliceKey := h.modulus(&key)
151 |
152 | h.list[sliceKey].lock.Lock()
153 | defer h.list[sliceKey].lock.Unlock()
154 |
155 | previous, expirationTime, ok = h.list[sliceKey].lru.Peek(key)
156 | if ok {
157 | return previous, true, false
158 | }
159 |
160 | evicted = h.list[sliceKey].lru.Add(key, value, expirationTime)
161 | return nil, false, evicted
162 | }
163 |
164 | // Remove removes the provided key from the cache.
165 | // Remove 从缓存中移除提供的键。
166 | func (h *HashLruCache) Remove(key interface{}) (present bool) {
167 | sliceKey := h.modulus(&key)
168 |
169 | h.list[sliceKey].lock.Lock()
170 | present = h.list[sliceKey].lru.Remove(key)
171 | h.list[sliceKey].lock.Unlock()
172 | return
173 | }
174 |
175 | // Resize changes the cache size.
176 | // Resize 调整缓存大小,返回调整前的数量
177 | func (h *HashLruCache) Resize(size int) (evicted int) {
178 | if size < h.sliceNum {
179 | size = h.sliceNum
180 | }
181 |
182 | // 计算出每个分片的数据长度
183 | lruLen := int(math.Ceil(float64(size / h.sliceNum)))
184 |
185 | for i := 0; i < h.sliceNum; i++ {
186 | h.list[i].lock.Lock()
187 | evicted = h.list[i].lru.Resize(lruLen)
188 | h.list[i].lock.Unlock()
189 | }
190 | return evicted
191 | }
192 |
193 | // Keys returns a slice of the keys in the cache, from oldest to newest.
194 | // Keys 返回缓存的切片,从最老的到最新的。
195 | func (h *HashLruCache) Keys() []interface{} {
196 |
197 | var keys []interface{}
198 |
199 | allKeys := make([][]interface{}, h.sliceNum)
200 |
201 | // 记录最大的 oneKeys 长度
202 | var oneKeysMaxLen int
203 |
204 | for s := 0; s < h.sliceNum; s++ {
205 | h.list[s].lock.RLock()
206 |
207 | if h.list[s].lru.Len() > oneKeysMaxLen {
208 | oneKeysMaxLen = h.list[s].lru.Len()
209 | }
210 |
211 | oneKeys := make([]interface{}, h.list[s].lru.Len())
212 | oneKeys = h.list[s].lru.Keys()
213 | h.list[s].lock.RUnlock()
214 |
215 | allKeys[s] = oneKeys
216 | }
217 |
218 | for c := 0; c < len(allKeys); c++ {
219 | for _, v := range allKeys[c] {
220 | keys = append(keys, v)
221 | }
222 | }
223 |
224 | //for i := 0; i < h.list[0].lru.Len(); i++ {
225 | // for c := 0; c < len(allKeys); c++ {
226 | // if len(allKeys[c]) > i {
227 | // keys = append(keys, allKeys[c][i])
228 | // }
229 | // }
230 | //}
231 |
232 | return keys
233 | }
234 |
235 | // Len returns the number of items in the cache.
236 | // Len 获取缓存已存在的缓存条数
237 | func (h *HashLruCache) Len() int {
238 | var length = 0
239 |
240 | for i := 0; i < h.sliceNum; i++ {
241 | h.list[i].lock.RLock()
242 | length = length + h.list[i].lru.Len()
243 | h.list[i].lock.RUnlock()
244 | }
245 | return length
246 | }
247 |
248 | func (h *HashLruCache) modulus(key *interface{}) int {
249 | str := InterfaceToString(*key)
250 | return int(md5.Sum([]byte(str))[0]) % h.sliceNum
251 | }
252 |
--------------------------------------------------------------------------------
/hashLru_test.go:
--------------------------------------------------------------------------------
1 | package mcache
2 |
3 | import (
4 | "math/rand"
5 | "runtime"
6 | "strconv"
7 | "sync"
8 | "testing"
9 | )
10 |
11 | func BenchmarkHashLRU_Rand(b *testing.B) {
12 | l, err := NewHashLRU(8192, 0)
13 | if err != nil {
14 | b.Fatalf("err: %v", err)
15 | }
16 |
17 | trace := make([]int64, b.N*2)
18 | for i := 0; i < b.N*2; i++ {
19 | trace[i] = rand.Int63() % 32768
20 | }
21 |
22 | b.ResetTimer()
23 |
24 | var hit, miss int
25 | for i := 0; i < 2*b.N; i++ {
26 | if i%2 == 0 {
27 | l.Add(trace[i], trace[i], 0)
28 | } else {
29 | _, _, ok := l.Get(trace[i])
30 | if ok {
31 | hit++
32 | } else {
33 | miss++
34 | }
35 | }
36 | }
37 | b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss))
38 | }
39 |
40 | func BenchmarkHashLRU_Freq(b *testing.B) {
41 | l, err := NewHashLRU(8192, 0)
42 | if err != nil {
43 | b.Fatalf("err: %v", err)
44 | }
45 |
46 | trace := make([]int64, b.N*2)
47 | for i := 0; i < b.N*2; i++ {
48 | if i%2 == 0 {
49 | trace[i] = rand.Int63() % 16384
50 | } else {
51 | trace[i] = rand.Int63() % 32768
52 | }
53 | }
54 |
55 | b.ResetTimer()
56 |
57 | for i := 0; i < b.N; i++ {
58 | l.Add(trace[i], trace[i], 0)
59 | }
60 | var hit, miss int
61 | for i := 0; i < b.N; i++ {
62 | _, _, ok := l.Get(trace[i])
63 | if ok {
64 | hit++
65 | } else {
66 | miss++
67 | }
68 | }
69 | b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss))
70 | }
71 |
72 | func TestHashLRUKeys(t *testing.T) {
73 | evictCounter := 0
74 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
75 | if k != v {
76 | t.Fatalf("Evict values not equal (%v!=%v)", k, v)
77 | }
78 | evictCounter++
79 | }
80 | l, err := NewHashLfuWithEvict(128, 8, onEvicted)
81 | if err != nil {
82 | t.Fatalf("err: %v", err)
83 | }
84 |
85 | // 每个切片对应的长度
86 | lenMap := map[int]int{
87 | 1: 1,
88 | 2: 2,
89 | 3: 0,
90 | 4: 2,
91 | 5: 3,
92 | }
93 | lenCount := make(map[int]int, 8)
94 |
95 | // 获取总key
96 | var totalCount int
97 | for _, v := range lenMap {
98 | totalCount += v
99 | }
100 |
101 | for i := 0; i < 128; i++ {
102 | var ak interface{} = i
103 | // 获取切片索引
104 | av := l.modulus(&ak)
105 | // 获取对应切片长度
106 | if lMax, ok := lenMap[av]; ok {
107 | // 对应切片计数
108 | curNum := lenCount[av]
109 | if curNum < lMax {
110 | l.Add(ak, i, 0)
111 | }
112 | lenCount[av]++
113 | } else {
114 | continue
115 | }
116 | }
117 |
118 | keyLen := len(l.Keys())
119 | if totalCount != len(l.Keys()) {
120 | t.Fatalf("Evict values not equal (%v!=%v)", totalCount, keyLen)
121 | }
122 |
123 | }
124 |
125 | func TestHashLRU(t *testing.T) {
126 | evictCounter := 0
127 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
128 | if k != v {
129 | t.Fatalf("Evict values not equal (%v!=%v)", k, v)
130 | }
131 | evictCounter++
132 | }
133 | l, err := NewHashLruWithEvict(128, 0, onEvicted)
134 | if err != nil {
135 | t.Fatalf("err: %v", err)
136 | }
137 |
138 | for i := 0; i < 256; i++ {
139 | l.Add(i, i, 0)
140 | }
141 |
142 | if l.Len() != 128 {
143 | t.Fatalf("bad len: %v", l.Len())
144 | }
145 |
146 | if evictCounter != 128 {
147 | t.Fatalf("bad evict count: %v", evictCounter)
148 | }
149 |
150 | for _, k := range l.Keys() {
151 | if v, _, ok := l.Get(k); !ok || v != k {
152 | t.Fatalf("bad key: %v, val: %v", k, v)
153 | }
154 | }
155 |
156 | for i := 128; i < 192; i++ {
157 | l.Remove(i)
158 | _, _, ok := l.Get(i)
159 | if ok {
160 | t.Fatalf("should be deleted")
161 | }
162 | }
163 |
164 | l.Purge()
165 | if l.Len() != 0 {
166 | t.Fatalf("bad len: %v", l.Len())
167 | }
168 | if _, _, ok := l.Get(200); ok {
169 | t.Fatalf("should contain nothing")
170 | }
171 | }
172 |
173 | // test that Add returns true/false if an eviction occurred
174 | func TestHashLRUAdd(t *testing.T) {
175 | evictCounter := 0
176 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
177 | evictCounter++
178 | }
179 |
180 | l, err := NewLruWithEvict(1, onEvicted)
181 | if err != nil {
182 | t.Fatalf("err: %v", err)
183 | }
184 |
185 | if l.Add(1, 1, 0) == false || evictCounter != 0 {
186 | t.Errorf("should not have an eviction")
187 | }
188 | if l.Add(2, 2, 0) == false || evictCounter != 1 {
189 | t.Errorf("should have an eviction")
190 | }
191 | }
192 |
193 | // test that Contains doesn't update recent-ness
194 | func TestHashLRUContains(t *testing.T) {
195 | l, err := NewLRU(2)
196 | if err != nil {
197 | t.Fatalf("err: %v", err)
198 | }
199 |
200 | l.Add(1, 1, 0)
201 | l.Add(2, 2, 0)
202 | if !l.Contains(1) {
203 | t.Errorf("1 should be contained")
204 | }
205 |
206 | l.Add(3, 3, 0)
207 | if l.Contains(1) {
208 | t.Errorf("Contains should not have updated recent-ness of 1")
209 | }
210 | }
211 |
212 | // test that ContainsOrAdd doesn't update recent-ness
213 | func TestHashLRUContainsOrAdd(t *testing.T) {
214 | l, err := NewLRU(2)
215 | if err != nil {
216 | t.Fatalf("err: %v", err)
217 | }
218 |
219 | l.Add(1, 1, 0)
220 | l.Add(2, 2, 0)
221 | contains, evict := l.ContainsOrAdd(1, 1, 0)
222 | if !contains {
223 | t.Errorf("1 should be contained")
224 | }
225 | if evict {
226 | t.Errorf("nothing should be evicted here")
227 | }
228 |
229 | l.Add(3, 3, 0)
230 | contains, evict = l.ContainsOrAdd(1, 1, 0)
231 | if contains {
232 | t.Errorf("1 should not have been contained")
233 | }
234 | if !evict {
235 | t.Errorf("an eviction should have occurred")
236 | }
237 | if !l.Contains(1) {
238 | t.Errorf("now 1 should be contained")
239 | }
240 | }
241 |
242 | // test that PeekOrAdd doesn't update recent-ness
243 | func TestHashLRUPeekOrAdd(t *testing.T) {
244 | l, err := NewLRU(2)
245 | if err != nil {
246 | t.Fatalf("err: %v", err)
247 | }
248 |
249 | l.Add(1, 1, 0)
250 | l.Add(2, 2, 0)
251 | previous, contains, evict := l.PeekOrAdd(1, 1, 0)
252 | if !contains {
253 | t.Errorf("1 should be contained")
254 | }
255 | if evict {
256 | t.Errorf("nothing should be evicted here")
257 | }
258 | if previous != 1 {
259 | t.Errorf("previous is not equal to 1")
260 | }
261 |
262 | l.Add(3, 3, 0)
263 | contains, evict = l.ContainsOrAdd(1, 1, 0)
264 | if contains {
265 | t.Errorf("1 should not have been contained")
266 | }
267 | if !evict {
268 | t.Errorf("an eviction should have occurred")
269 | }
270 | if !l.Contains(1) {
271 | t.Errorf("now 1 should be contained")
272 | }
273 | }
274 |
275 | // test that Peek doesn't update recent-ness
276 | func TestHashLRUPeek(t *testing.T) {
277 | l, err := NewLRU(2)
278 | if err != nil {
279 | t.Fatalf("err: %v", err)
280 | }
281 |
282 | l.Add(1, 1, 0)
283 | l.Add(2, 2, 0)
284 | if v, _, ok := l.Peek(1); !ok || v != 1 {
285 | t.Errorf("1 should be set to 1: %v, %v", v, ok)
286 | }
287 |
288 | l.Add(3, 3, 0)
289 | if l.Contains(1) {
290 | t.Errorf("should not have updated recent-ness of 1")
291 | }
292 | }
293 |
294 | // test that Resize can upsize and downsize
295 | func TestHashLRUResize(t *testing.T) {
296 | onEvictCounter := 0
297 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
298 | onEvictCounter++
299 | }
300 | l, err := NewLruWithEvict(2, onEvicted)
301 | if err != nil {
302 | t.Fatalf("err: %v", err)
303 | }
304 |
305 | // Downsize
306 | l.Add(1, 1, 0)
307 | l.Add(2, 2, 0)
308 | evicted := l.Resize(1)
309 | if evicted != 1 {
310 | t.Errorf("1 element should have been evicted: %v", evicted)
311 | }
312 | if onEvictCounter != 1 {
313 | t.Errorf("onEvicted should have been called 1 time: %v", onEvictCounter)
314 | }
315 |
316 | l.Add(3, 3, 0)
317 | if l.Contains(1) {
318 | t.Errorf("Element 1 should have been evicted")
319 | }
320 |
321 | // Upsize
322 | evicted = l.Resize(2)
323 | if evicted != 0 {
324 | t.Errorf("0 elements should have been evicted: %v", evicted)
325 | }
326 |
327 | l.Add(4, 4, 0)
328 | if !l.Contains(3) || !l.Contains(4) {
329 | t.Errorf("lruCache should have contained 2 elements")
330 | }
331 | }
332 |
333 | // HashLRU 性能压测
334 | func TestHashLRU_Performance(t *testing.T) {
335 | runtime.GOMAXPROCS(runtime.NumCPU())
336 | //runtime.GOMAXPROCS(1)
337 |
338 | //cpu 性能分析 go tool pprof --pdf cpu ./cpu.pprof > cpu2.pdf
339 | //开始性能分析, 返回一个停止接口
340 | //stopper1 := profile.Start(profile.CPUProfile, profile.ProfilePath("."))
341 | ////在main()结束时停止性能分析
342 | //defer stopper1.Stop()
343 |
344 | //// 查看导致阻塞同步的堆栈跟踪
345 | //stopper2 := profile.Start(profile.BlockProfile, profile.ProfilePath("."))
346 | //// 在main()结束时停止性能分析
347 | //defer stopper2.Stop()
348 | //
349 | //// 查看当前所有运行的 goroutines 堆栈跟踪
350 | //stopper3 := profile.Start(profile.GoroutineProfile, profile.ProfilePath("."))
351 | //// 在main()结束时停止性能分析
352 | //defer stopper3.Stop()
353 | //
354 | //// 查看当前所有运行的 goroutines 堆栈跟踪
355 | //stopper4 := profile.Start(profile.MemProfile, profile.ProfilePath("."))
356 | //// 在main()结束时停止性能分析
357 | //defer stopper4.Stop()
358 |
359 | count := 10000000
360 | l, _ := NewHashLRU(20000, 64)
361 |
362 | wg := &sync.WaitGroup{}
363 | for k := 0; k < count; k++ {
364 | wg.Add(1)
365 | go HashlruPerformanceOne(l, wg, k)
366 | }
367 | wg.Wait()
368 |
369 | }
370 |
371 | func HashlruPerformanceOne(h *HashLruCache, c *sync.WaitGroup, k int) {
372 |
373 | for i := 0; i < 5; i++ {
374 |
375 | var strKey string
376 | strKey = strconv.Itoa(k) + "_" + strconv.Itoa(i)
377 |
378 | h.Add(strKey, &testJsonStr, 0)
379 |
380 | }
381 |
382 | // 通知main已经结束循环(我搞定了!)
383 | c.Done()
384 | }
385 |
--------------------------------------------------------------------------------
/lfu.go:
--------------------------------------------------------------------------------
1 | package mcache
2 |
3 | import (
4 | "github.com/songangweb/mcache/simplelfu"
5 | "sync"
6 | )
7 |
8 | // LfuCache is a thread-safe fixed size LRU cache.
9 | // LfuCache 实现一个给定大小的LFU缓存
10 | type LfuCache struct {
11 | lfu simplelfu.LFUCache
12 | lock sync.RWMutex
13 | }
14 |
15 | // NewLFU creates an LRU of the given size.
16 | // NewLRU 构造一个给定大小的LRU
17 | func NewLFU(size int) (*LfuCache, error) {
18 | return NewLfuWithEvict(size, nil)
19 | }
20 |
21 | // NewLfuWithEvict constructs a fixed size cache with the given eviction
22 | // callback.
23 | // NewLruWithEvict 用于在缓存条目被淘汰时的回调函数
24 | func NewLfuWithEvict(size int, onEvicted func(key interface{}, value interface{}, expirationTime int64)) (*LfuCache, error) {
25 | lfu, _ := simplelfu.NewLFU(size, simplelfu.EvictCallback(onEvicted))
26 | c := &LfuCache{
27 | lfu: lfu,
28 | }
29 | return c, nil
30 | }
31 |
32 | // Purge is used to completely clear the cache.
33 | // Purge 用于完全清除缓存
34 | func (c *LfuCache) Purge() {
35 | c.lock.Lock()
36 | c.lfu.Purge()
37 | c.lock.Unlock()
38 | }
39 |
40 | // PurgeOverdue is used to completely clear the overdue cache.
41 | // PurgeOverdue 用于清除过期缓存。
42 | func (c *LfuCache) PurgeOverdue() {
43 | c.lock.Lock()
44 | c.lfu.PurgeOverdue()
45 | c.lock.Unlock()
46 | }
47 |
48 | // Add adds a value to the cache. Returns true if an eviction occurred.
49 | // Add 向缓存添加一个值。如果已经存在,则更新信息
50 | func (c *LfuCache) Add(key, value interface{}, expirationTime int64) (evicted bool) {
51 | c.lock.Lock()
52 | evicted = c.lfu.Add(key, value, expirationTime)
53 | c.lock.Unlock()
54 | return evicted
55 | }
56 |
57 | // Get looks up a key's value from the cache.
58 | // Get 从缓存中查找一个键的值
59 | func (c *LfuCache) Get(key interface{}) (value interface{}, expirationTime int64, ok bool) {
60 | c.lock.Lock()
61 | value, expirationTime, ok = c.lfu.Get(key)
62 | c.lock.Unlock()
63 | return value, expirationTime, ok
64 | }
65 |
66 | // Contains checks if a key is in the cache, without updating the
67 | // recent-ness or deleting it for being stale.
68 | // Contains 检查某个键是否在缓存中,但不更新缓存的状态
69 | func (c *LfuCache) Contains(key interface{}) bool {
70 | c.lock.RLock()
71 | containKey := c.lfu.Contains(key)
72 | c.lock.RUnlock()
73 | return containKey
74 | }
75 |
76 | // Peek returns the key value (or undefined if not found) without updating
77 | // the "recently used"-ness of the key.
78 | // Peek 在不更新的情况下返回键值(如果没有找到则返回false),不更新缓存的状态
79 | func (c *LfuCache) Peek(key interface{}) (value interface{}, expirationTime int64, ok bool) {
80 | c.lock.RLock()
81 | value, expirationTime, ok = c.lfu.Peek(key)
82 | c.lock.RUnlock()
83 | return value, expirationTime, ok
84 | }
85 |
86 | // ContainsOrAdd checks if a key is in the cache without updating the
87 | // recent-ness or deleting it for being stale, and if not, adds the value.
88 | // Returns whether found and whether an eviction occurred.
89 | // ContainsOrAdd 判断是否已经存在于缓存中,如果已经存在则不创建及更新内容
90 | func (c *LfuCache) ContainsOrAdd(key, value interface{}, expirationTime int64) (ok, evicted bool) {
91 | c.lock.Lock()
92 | defer c.lock.Unlock()
93 |
94 | if c.lfu.Contains(key) {
95 | return true, false
96 | }
97 | evicted = c.lfu.Add(key, value, expirationTime)
98 | return false, evicted
99 | }
100 |
101 | // PeekOrAdd checks if a key is in the cache without updating the
102 | // recent-ness or deleting it for being stale, and if not, adds the value.
103 | // Returns whether found and whether an eviction occurred.
104 | // PeekOrAdd 判断是否已经存在于缓存中,如果已经存在则不更新其键的使用状态
105 | func (c *LfuCache) PeekOrAdd(key, value interface{}, expirationTime int64) (previous interface{}, ok, evicted bool) {
106 | c.lock.Lock()
107 | defer c.lock.Unlock()
108 |
109 | previous, expirationTime, ok = c.lfu.Peek(key)
110 | if ok {
111 | return previous, true, false
112 | }
113 |
114 | evicted = c.lfu.Add(key, value, expirationTime)
115 | return nil, false, evicted
116 | }
117 |
118 | // Remove removes the provided key from the cache.
119 | // Remove 从缓存中移除提供的键
120 | func (c *LfuCache) Remove(key interface{}) (present bool) {
121 | c.lock.Lock()
122 | present = c.lfu.Remove(key)
123 | c.lock.Unlock()
124 | return
125 | }
126 |
127 | // Resize changes the cache size.
128 | // Resize 调整缓存大小,返回调整前的数量
129 | func (c *LfuCache) Resize(size int) (evicted int) {
130 | c.lock.Lock()
131 | evicted = c.lfu.Resize(size)
132 | c.lock.Unlock()
133 | return evicted
134 | }
135 |
136 | // ResizeWeight 改变缓存中Weight大小。
137 | // ResizeWeight 改变缓存中Weight大小。
138 | func (c *LfuCache) ResizeWeight(percentage int){
139 | c.lock.Lock()
140 | c.lfu.ResizeWeight(percentage)
141 | c.lock.Unlock()
142 | }
143 |
144 | // RemoveOldest removes the oldest item from the cache.
145 | // RemoveOldest 从缓存中移除最老的项
146 | func (c *LfuCache) RemoveOldest() (key interface{}, value interface{}, expirationTime int64, ok bool) {
147 | c.lock.Lock()
148 | key, value, expirationTime, ok = c.lfu.RemoveOldest()
149 | c.lock.Unlock()
150 | return
151 | }
152 |
153 | // GetOldest returns the oldest entry
154 | // GetOldest 返回最老的条目
155 | func (c *LfuCache) GetOldest() (key interface{}, value interface{}, expirationTime int64, ok bool) {
156 | c.lock.Lock()
157 | key, value, expirationTime, ok = c.lfu.GetOldest()
158 | c.lock.Unlock()
159 | return
160 | }
161 |
162 | // Keys returns a slice of the keys in the cache, from oldest to newest.
163 | // Keys 返回缓存中键的切片,从最老的到最新的
164 | func (c *LfuCache) Keys() []interface{} {
165 | c.lock.RLock()
166 | keys := c.lfu.Keys()
167 | c.lock.RUnlock()
168 | return keys
169 | }
170 |
171 | // Len returns the number of items in the cache.
172 | // Len 获取缓存已存在的缓存条数
173 | func (c *LfuCache) Len() int {
174 | c.lock.RLock()
175 | length := c.lfu.Len()
176 | c.lock.RUnlock()
177 | return length
178 | }
179 |
--------------------------------------------------------------------------------
/lfu_test.go:
--------------------------------------------------------------------------------
1 | package mcache
2 |
3 | import (
4 | "math/rand"
5 | "runtime"
6 | "strconv"
7 | "sync"
8 | "testing"
9 | )
10 |
11 | func BenchmarkLFU_Rand(b *testing.B) {
12 | l, err := NewLFU(8192)
13 | if err != nil {
14 | b.Fatalf("err: %v", err)
15 | }
16 |
17 | trace := make([]int64, b.N*2)
18 | for i := 0; i < b.N*2; i++ {
19 | trace[i] = rand.Int63() % 32768
20 | }
21 |
22 | b.ResetTimer()
23 |
24 | var hit, miss int
25 | for i := 0; i < 2*b.N; i++ {
26 | if i%2 == 0 {
27 | l.Add(trace[i], trace[i], 0)
28 | } else {
29 | _, _, ok := l.Get(trace[i])
30 | if ok {
31 | hit++
32 | } else {
33 | miss++
34 | }
35 | }
36 | }
37 | b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss))
38 | }
39 |
40 | func BenchmarkLFU_Freq(b *testing.B) {
41 | l, err := NewLFU(8192)
42 | if err != nil {
43 | b.Fatalf("err: %v", err)
44 | }
45 |
46 | trace := make([]int64, b.N*2)
47 | for i := 0; i < b.N*2; i++ {
48 | if i%2 == 0 {
49 | trace[i] = rand.Int63() % 16384
50 | } else {
51 | trace[i] = rand.Int63() % 32768
52 | }
53 | }
54 |
55 | b.ResetTimer()
56 |
57 | for i := 0; i < b.N; i++ {
58 | l.Add(trace[i], trace[i], 0)
59 | }
60 | var hit, miss int
61 | for i := 0; i < b.N; i++ {
62 | _, _, ok := l.Get(trace[i])
63 | if ok {
64 | hit++
65 | } else {
66 | miss++
67 | }
68 | }
69 | b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss))
70 | }
71 |
72 | func TestLFU(t *testing.T) {
73 | evictCounter := 0
74 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
75 | if k != v {
76 | t.Fatalf("Evict values not equal (%v!=%v)", k, v)
77 | }
78 | evictCounter++
79 | }
80 | l, err := NewLruWithEvict(128, onEvicted)
81 | if err != nil {
82 | t.Fatalf("err: %v", err)
83 | }
84 |
85 | for i := 0; i < 256; i++ {
86 | l.Add(i, i, 0)
87 | }
88 |
89 | if l.Len() != 128 {
90 | t.Fatalf("bad len: %v", l.Len())
91 | }
92 |
93 | if evictCounter != 128 {
94 | t.Fatalf("bad evict count: %v", evictCounter)
95 | }
96 |
97 | for i, k := range l.Keys() {
98 | if v, _, ok := l.Get(k); !ok || v != k || v != i+128 {
99 | t.Fatalf("bad key: %v", k)
100 | }
101 | }
102 | for i := 0; i < 128; i++ {
103 | _, _, ok := l.Get(i)
104 | if ok {
105 | t.Fatalf("should be evicted")
106 | }
107 | }
108 | for i := 128; i < 256; i++ {
109 | _, _, ok := l.Get(i)
110 | if !ok {
111 | t.Fatalf("should not be evicted")
112 | }
113 | }
114 | for i := 128; i < 192; i++ {
115 | l.Remove(i)
116 | _, _, ok := l.Get(i)
117 | if ok {
118 | t.Fatalf("should be deleted")
119 | }
120 | }
121 |
122 | l.Get(192) // expect 192 to be last key in l.Keys()
123 |
124 | for i, k := range l.Keys() {
125 | if (i < 63 && k != i+193) || (i == 63 && k != 192) {
126 | t.Fatalf("out of order key: %v", k)
127 | }
128 | }
129 |
130 | l.Purge()
131 | if l.Len() != 0 {
132 | t.Fatalf("bad len: %v", l.Len())
133 | }
134 | if _, _, ok := l.Get(200); ok {
135 | t.Fatalf("should contain nothing")
136 | }
137 | }
138 |
139 | // test that Add returns true/false if an eviction occurred
140 | func TestLFUAdd(t *testing.T) {
141 | evictCounter := 0
142 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
143 | evictCounter++
144 | }
145 |
146 | l, err := NewLfuWithEvict(1, onEvicted)
147 | if err != nil {
148 | t.Fatalf("err: %v", err)
149 | }
150 |
151 | if l.Add(1, 1, 0) == false || evictCounter != 0 {
152 | t.Errorf("should not have an eviction")
153 | }
154 | if l.Add(2, 2, 0) == false || evictCounter != 1 {
155 | t.Errorf("should have an eviction")
156 | }
157 | }
158 |
159 | // test that Contains doesn't update recent-ness
160 | func TestLFUContains(t *testing.T) {
161 | l, err := NewLRU(2)
162 | if err != nil {
163 | t.Fatalf("err: %v", err)
164 | }
165 |
166 | l.Add(1, 1, 0)
167 | l.Add(2, 2, 0)
168 | if !l.Contains(1) {
169 | t.Errorf("1 should be contained")
170 | }
171 |
172 | l.Add(3, 3, 0)
173 | if l.Contains(1) {
174 | t.Errorf("Contains should not have updated recent-ness of 1")
175 | }
176 | }
177 |
178 | // test that ContainsOrAdd doesn't update recent-ness
179 | func TestLFUContainsOrAdd(t *testing.T) {
180 | l, err := NewLRU(2)
181 | if err != nil {
182 | t.Fatalf("err: %v", err)
183 | }
184 |
185 | l.Add(1, 1, 0)
186 | l.Add(2, 2, 0)
187 | contains, evict := l.ContainsOrAdd(1, 1, 0)
188 | if !contains {
189 | t.Errorf("1 should be contained")
190 | }
191 | if evict {
192 | t.Errorf("nothing should be evicted here")
193 | }
194 |
195 | l.Add(3, 3, 0)
196 | contains, evict = l.ContainsOrAdd(1, 1, 0)
197 | if contains {
198 | t.Errorf("1 should not have been contained")
199 | }
200 | if !evict {
201 | t.Errorf("an eviction should have occurred")
202 | }
203 | if !l.Contains(1) {
204 | t.Errorf("now 1 should be contained")
205 | }
206 | }
207 |
208 | // test that PeekOrAdd doesn't update recent-ness
209 | func TestLFUPeekOrAdd(t *testing.T) {
210 | l, err := NewLRU(2)
211 | if err != nil {
212 | t.Fatalf("err: %v", err)
213 | }
214 |
215 | l.Add(1, 1, 0)
216 | l.Add(2, 2, 0)
217 | previous, contains, evict := l.PeekOrAdd(1, 1, 0)
218 | if !contains {
219 | t.Errorf("1 should be contained")
220 | }
221 | if evict {
222 | t.Errorf("nothing should be evicted here")
223 | }
224 | if previous != 1 {
225 | t.Errorf("previous is not equal to 1")
226 | }
227 |
228 | l.Add(3, 3, 0)
229 | contains, evict = l.ContainsOrAdd(1, 1, 0)
230 | if contains {
231 | t.Errorf("1 should not have been contained")
232 | }
233 | if !evict {
234 | t.Errorf("an eviction should have occurred")
235 | }
236 | if !l.Contains(1) {
237 | t.Errorf("now 1 should be contained")
238 | }
239 | }
240 |
241 | // test that Peek doesn't update recent-ness
242 | func TestLFUPeek(t *testing.T) {
243 | l, err := NewLRU(2)
244 | if err != nil {
245 | t.Fatalf("err: %v", err)
246 | }
247 |
248 | l.Add(1, 1, 0)
249 | l.Add(2, 2, 0)
250 | if v, _, ok := l.Peek(1); !ok || v != 1 {
251 | t.Errorf("1 should be set to 1: %v, %v", v, ok)
252 | }
253 |
254 | l.Add(3, 3, 0)
255 | if l.Contains(1) {
256 | t.Errorf("should not have updated recent-ness of 1")
257 | }
258 | }
259 |
260 | // test that Resize can upsize and downsize
261 | func TestLFUResize(t *testing.T) {
262 | onEvictCounter := 0
263 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
264 | onEvictCounter++
265 | }
266 | l, err := NewLfuWithEvict(2, onEvicted)
267 | if err != nil {
268 | t.Fatalf("err: %v", err)
269 | }
270 |
271 | // Downsize
272 | l.Add(1, 1, 0)
273 | l.Add(2, 2, 0)
274 | evicted := l.Resize(1)
275 | if evicted != 1 {
276 | t.Errorf("1 element should have been evicted: %v", evicted)
277 | }
278 | if onEvictCounter != 1 {
279 | t.Errorf("onEvicted should have been called 1 time: %v", onEvictCounter)
280 | }
281 |
282 | l.Add(3, 3, 0)
283 | if l.Contains(1) {
284 | t.Errorf("Element 1 should have been evicted")
285 | }
286 |
287 | // Upsize
288 | evicted = l.Resize(2)
289 | if evicted != 0 {
290 | t.Errorf("0 elements should have been evicted: %v", evicted)
291 | }
292 |
293 | l.Add(4, 4, 0)
294 | if !l.Contains(3) || !l.Contains(4) {
295 | t.Errorf("lruCache should have contained 2 elements")
296 | }
297 | }
298 |
299 |
300 |
301 | // LFU 性能压测
302 | func TestLFU_Performance(t *testing.T) {
303 | runtime.GOMAXPROCS(runtime.NumCPU())
304 |
305 | //cpu 性能分析 go tool pprof --pdf cpu ./cpu.pprof > cpu.pdf
306 | //开始性能分析, 返回一个停止接口
307 | //stopper1 := profile.Start(profile.CPUProfile, profile.ProfilePath("."))
308 | ////在main()结束时停止性能分析
309 | //defer stopper1.Stop()
310 |
311 | //// 查看导致阻塞同步的堆栈跟踪
312 | //stopper2 := profile.Start(profile.BlockProfile, profile.ProfilePath("."))
313 | //// 在main()结束时停止性能分析
314 | //defer stopper2.Stop()
315 | //
316 | //// 查看当前所有运行的 goroutines 堆栈跟踪
317 | //stopper3 := profile.Start(profile.GoroutineProfile, profile.ProfilePath("."))
318 | //// 在main()结束时停止性能分析
319 | //defer stopper3.Stop()
320 | //
321 | //// 查看当前所有运行的 goroutines 堆栈跟踪
322 | //stopper4 := profile.Start(profile.MemProfile, profile.ProfilePath("."))
323 | //// 在main()结束时停止性能分析
324 | //defer stopper4.Stop()
325 |
326 | count := 10000000
327 | l, _ := NewLFU(20000)
328 |
329 | wg := &sync.WaitGroup{}
330 | for k := 0; k < count; k++ {
331 | wg.Add(1)
332 | go lfuPerformanceOne(l, wg, k)
333 | }
334 | wg.Wait()
335 |
336 | }
337 |
338 |
339 | func lfuPerformanceOne(h *LfuCache, c *sync.WaitGroup, k int) {
340 |
341 | for i := 0; i < 5; i++ {
342 |
343 | var strKey string
344 | strKey = strconv.Itoa(k) + "_" + strconv.Itoa(i)
345 |
346 | h.Add(strKey, &testJsonStr, 0)
347 |
348 | }
349 |
350 | // 通知main已经结束循环(我搞定了!)
351 | c.Done()
352 | }
353 |
354 |
355 |
356 |
357 |
358 |
--------------------------------------------------------------------------------
/lru.go:
--------------------------------------------------------------------------------
1 | package mcache
2 |
3 | import (
4 | "github.com/songangweb/mcache/simplelru"
5 | "sync"
6 | )
7 |
8 | // LruCache is a thread-safe fixed size LRU cache.
9 | // LruCache 实现一个给定大小的LRU缓存
10 | type LruCache struct {
11 | lru simplelru.LRUCache
12 | lock sync.RWMutex
13 | }
14 |
15 | // NewLRU creates an LRU of the given size.
16 | // NewLRU 构造一个给定大小的LRU
17 | func NewLRU(size int) (*LruCache, error) {
18 | return NewLruWithEvict(size, nil)
19 | }
20 |
21 | // NewLruWithEvict constructs a fixed size cache with the given eviction
22 | // callback.
23 | // NewLruWithEvict 用于在缓存条目被淘汰时的回调函数
24 | func NewLruWithEvict(size int, onEvicted func(key interface{}, value interface{}, expirationTime int64)) (*LruCache, error) {
25 | lru, err := simplelru.NewLRU(size, simplelru.EvictCallback(onEvicted))
26 | if err != nil {
27 | return nil, err
28 | }
29 | c := &LruCache{
30 | lru: lru,
31 | }
32 | return c, nil
33 | }
34 |
35 | // Purge is used to completely clear the cache.
36 | // Purge 清除所有缓存项
37 | func (c *LruCache) Purge() {
38 | c.lock.Lock()
39 | c.lru.Purge()
40 | c.lock.Unlock()
41 | }
42 |
43 | // PurgeOverdue is used to completely clear the overdue cache.
44 | // PurgeOverdue 用于清除过期缓存。
45 | func (c *LruCache) PurgeOverdue() {
46 | c.lock.Lock()
47 | c.lru.PurgeOverdue()
48 | c.lock.Unlock()
49 | }
50 |
51 | // Add adds a value to the cache. Returns true if an eviction occurred.
52 | // Add 向缓存添加一个值。如果已经存在,则更新信息
53 | func (c *LruCache) Add(key, value interface{}, expirationTime int64) (evicted bool) {
54 | c.lock.Lock()
55 | evicted = c.lru.Add(key, value, expirationTime)
56 | c.lock.Unlock()
57 | return evicted
58 | }
59 |
60 | // Get looks up a key's value from the cache.
61 | // Get 从缓存中查找一个键的值。
62 | func (c *LruCache) Get(key interface{}) (value interface{}, expirationTime int64, ok bool) {
63 | c.lock.Lock()
64 | value, expirationTime, ok = c.lru.Get(key)
65 | c.lock.Unlock()
66 | return value, expirationTime, ok
67 | }
68 |
69 | // Contains checks if a key is in the cache, without updating the
70 | // recent-ness or deleting it for being stale.
71 | // Contains 检查某个键是否在缓存中,但不更新缓存的状态
72 | func (c *LruCache) Contains(key interface{}) bool {
73 | c.lock.RLock()
74 | containKey := c.lru.Contains(key)
75 | c.lock.RUnlock()
76 | return containKey
77 | }
78 |
79 | // Peek returns the key value (or undefined if not found) without updating
80 | // the "recently used"-ness of the key.
81 | // Peek 在不更新的情况下返回键值(如果没有找到则返回false),不更新缓存的状态
82 | func (c *LruCache) Peek(key interface{}) (value interface{}, expirationTime int64, ok bool) {
83 | c.lock.RLock()
84 | value, expirationTime, ok = c.lru.Peek(key)
85 | c.lock.RUnlock()
86 | return value, expirationTime, ok
87 | }
88 |
89 | // ContainsOrAdd checks if a key is in the cache without updating the
90 | // recent-ness or deleting it for being stale, and if not, adds the value.
91 | // Returns whether found and whether an eviction occurred.
92 | // ContainsOrAdd 检查键是否在缓存中,而不更新
93 | // 最近或删除它,因为它是陈旧的,如果不是,添加值。
94 | // 返回是否找到和是否发生了驱逐。
95 | func (c *LruCache) ContainsOrAdd(key, value interface{}, expirationTime int64) (ok, evicted bool) {
96 | c.lock.Lock()
97 | defer c.lock.Unlock()
98 |
99 | if c.lru.Contains(key) {
100 | return true, false
101 | }
102 | evicted = c.lru.Add(key, value, expirationTime)
103 | return false, evicted
104 | }
105 |
106 | // PeekOrAdd checks if a key is in the cache without updating the
107 | // recent-ness or deleting it for being stale, and if not, adds the value.
108 | // Returns whether found and whether an eviction occurred.
109 | // PeekOrAdd 如果一个key在缓存中,那么这个key就不会被更新
110 | // 最近或删除它,因为它是陈旧的,如果不是,添加值。
111 | // 返回是否找到和是否发生了驱逐。
112 | func (c *LruCache) PeekOrAdd(key, value interface{}, expirationTime int64) (previous interface{}, ok, evicted bool) {
113 | c.lock.Lock()
114 | defer c.lock.Unlock()
115 |
116 | previous, expirationTime, ok = c.lru.Peek(key)
117 | if ok {
118 | return previous, true, false
119 | }
120 |
121 | evicted = c.lru.Add(key, value, expirationTime)
122 | return nil, false, evicted
123 | }
124 |
125 | // Remove removes the provided key from the cache.
126 | // Remove 从缓存中移除提供的键。
127 | func (c *LruCache) Remove(key interface{}) (present bool) {
128 | c.lock.Lock()
129 | present = c.lru.Remove(key)
130 | c.lock.Unlock()
131 | return
132 | }
133 |
134 | // Resize changes the cache size.
135 | // Resize 调整缓存大小,返回调整前的数量
136 | func (c *LruCache) Resize(size int) (evicted int) {
137 | c.lock.Lock()
138 | evicted = c.lru.Resize(size)
139 | c.lock.Unlock()
140 | return evicted
141 | }
142 |
143 | // RemoveOldest removes the oldest item from the cache.
144 | // RemoveOldest 从缓存中移除最老的项
145 | func (c *LruCache) RemoveOldest() (key interface{}, value interface{}, expirationTime int64, ok bool) {
146 | c.lock.Lock()
147 | key, value, expirationTime, ok = c.lru.RemoveOldest()
148 | c.lock.Unlock()
149 | return
150 | }
151 |
152 | // GetOldest returns the oldest entry
153 | // GetOldest 从缓存中返回最旧的条目
154 | func (c *LruCache) GetOldest() (key interface{}, value interface{}, expirationTime int64, ok bool) {
155 | c.lock.Lock()
156 | key, value, expirationTime, ok = c.lru.GetOldest()
157 | c.lock.Unlock()
158 | return
159 | }
160 |
161 | // Keys returns a slice of the keys in the cache, from oldest to newest.
162 | // Keys 返回缓存中键的切片,从最老到最新
163 | func (c *LruCache) Keys() []interface{} {
164 | c.lock.RLock()
165 | keys := c.lru.Keys()
166 | c.lock.RUnlock()
167 | return keys
168 | }
169 |
170 | // Len returns the number of items in the cache.
171 | // Len 获取缓存已存在的缓存条数
172 | func (c *LruCache) Len() int {
173 | c.lock.RLock()
174 | length := c.lru.Len()
175 | c.lock.RUnlock()
176 | return length
177 | }
178 |
--------------------------------------------------------------------------------
/lru_test.go:
--------------------------------------------------------------------------------
1 | package mcache
2 |
3 | import (
4 | "math/rand"
5 | "runtime"
6 | "strconv"
7 | "sync"
8 | "testing"
9 | )
10 |
11 | func BenchmarkLRU_Rand(b *testing.B) {
12 | l, err := NewLFU(8192)
13 | if err != nil {
14 | b.Fatalf("err: %v", err)
15 | }
16 |
17 | trace := make([]int64, b.N*2)
18 | for i := 0; i < b.N*2; i++ {
19 | trace[i] = rand.Int63() % 32768
20 | }
21 |
22 | b.ResetTimer()
23 |
24 | var hit, miss int
25 | for i := 0; i < 2*b.N; i++ {
26 | if i%2 == 0 {
27 | l.Add(trace[i], trace[i], 0)
28 | } else {
29 | _, _, ok := l.Get(trace[i])
30 | if ok {
31 | hit++
32 | } else {
33 | miss++
34 | }
35 | }
36 | }
37 | b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss))
38 | }
39 |
40 | func BenchmarkLRU_Freq(b *testing.B) {
41 | l, err := NewLFU(8192)
42 | if err != nil {
43 | b.Fatalf("err: %v", err)
44 | }
45 |
46 | trace := make([]int64, b.N*2)
47 | for i := 0; i < b.N*2; i++ {
48 | if i%2 == 0 {
49 | trace[i] = rand.Int63() % 16384
50 | } else {
51 | trace[i] = rand.Int63() % 32768
52 | }
53 | }
54 |
55 | b.ResetTimer()
56 |
57 | for i := 0; i < b.N; i++ {
58 | l.Add(trace[i], trace[i], 0)
59 | }
60 | var hit, miss int
61 | for i := 0; i < b.N; i++ {
62 | _, _, ok := l.Get(trace[i])
63 | if ok {
64 | hit++
65 | } else {
66 | miss++
67 | }
68 | }
69 | b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss))
70 | }
71 |
72 | func TestLRU(t *testing.T) {
73 | evictCounter := 0
74 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
75 | if k != v {
76 | t.Fatalf("Evict values not equal (%v!=%v)", k, v)
77 | }
78 | evictCounter++
79 | }
80 | l, err := NewLruWithEvict(128, onEvicted)
81 | if err != nil {
82 | t.Fatalf("err: %v", err)
83 | }
84 |
85 | for i := 0; i < 256; i++ {
86 | l.Add(i, i, 0)
87 | }
88 |
89 | if l.Len() != 128 {
90 | t.Fatalf("bad len: %v", l.Len())
91 | }
92 |
93 | if evictCounter != 128 {
94 | t.Fatalf("bad evict count: %v", evictCounter)
95 | }
96 |
97 | for i, k := range l.Keys() {
98 | if v, _, ok := l.Get(k); !ok || v != k || v != i+128 {
99 | t.Fatalf("bad key: %v", k)
100 | }
101 | }
102 | for i := 0; i < 128; i++ {
103 | _, _, ok := l.Get(i)
104 | if ok {
105 | t.Fatalf("should be evicted")
106 | }
107 | }
108 | for i := 128; i < 256; i++ {
109 | _, _, ok := l.Get(i)
110 | if !ok {
111 | t.Fatalf("should not be evicted")
112 | }
113 | }
114 | for i := 128; i < 192; i++ {
115 | l.Remove(i)
116 | _, _, ok := l.Get(i)
117 | if ok {
118 | t.Fatalf("should be deleted")
119 | }
120 | }
121 |
122 | l.Get(192) // expect 192 to be last key in l.Keys()
123 |
124 | for i, k := range l.Keys() {
125 | if (i < 63 && k != i+193) || (i == 63 && k != 192) {
126 | t.Fatalf("out of order key: %v", k)
127 | }
128 | }
129 |
130 | l.Purge()
131 | if l.Len() != 0 {
132 | t.Fatalf("bad len: %v", l.Len())
133 | }
134 | if _, _, ok := l.Get(200); ok {
135 | t.Fatalf("should contain nothing")
136 | }
137 | }
138 |
139 | // test that Add returns true/false if an eviction occurred
140 | func TestLRUAdd(t *testing.T) {
141 | evictCounter := 0
142 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
143 | evictCounter++
144 | }
145 |
146 | l, err := NewLfuWithEvict(1, onEvicted)
147 | if err != nil {
148 | t.Fatalf("err: %v", err)
149 | }
150 |
151 | if l.Add(1, 1, 0) == false || evictCounter != 0 {
152 | t.Errorf("should not have an eviction")
153 | }
154 | if l.Add(2, 2, 0) == false || evictCounter != 1 {
155 | t.Errorf("should have an eviction")
156 | }
157 | }
158 |
159 | // test that Contains doesn't update recent-ness
160 | func TestLRUContains(t *testing.T) {
161 | l, err := NewLRU(2)
162 | if err != nil {
163 | t.Fatalf("err: %v", err)
164 | }
165 |
166 | l.Add(1, 1, 0)
167 | l.Add(2, 2, 0)
168 | if !l.Contains(1) {
169 | t.Errorf("1 should be contained")
170 | }
171 |
172 | l.Add(3, 3, 0)
173 | if l.Contains(1) {
174 | t.Errorf("Contains should not have updated recent-ness of 1")
175 | }
176 | }
177 |
178 | // test that ContainsOrAdd doesn't update recent-ness
179 | func TestLRUContainsOrAdd(t *testing.T) {
180 | l, err := NewLRU(2)
181 | if err != nil {
182 | t.Fatalf("err: %v", err)
183 | }
184 |
185 | l.Add(1, 1, 0)
186 | l.Add(2, 2, 0)
187 | contains, evict := l.ContainsOrAdd(1, 1, 0)
188 | if !contains {
189 | t.Errorf("1 should be contained")
190 | }
191 | if evict {
192 | t.Errorf("nothing should be evicted here")
193 | }
194 |
195 | l.Add(3, 3, 0)
196 | contains, evict = l.ContainsOrAdd(1, 1, 0)
197 | if contains {
198 | t.Errorf("1 should not have been contained")
199 | }
200 | if !evict {
201 | t.Errorf("an eviction should have occurred")
202 | }
203 | if !l.Contains(1) {
204 | t.Errorf("now 1 should be contained")
205 | }
206 | }
207 |
208 | // test that PeekOrAdd doesn't update recent-ness
209 | func TestLRUPeekOrAdd(t *testing.T) {
210 | l, err := NewLRU(2)
211 | if err != nil {
212 | t.Fatalf("err: %v", err)
213 | }
214 |
215 | l.Add(1, 1, 0)
216 | l.Add(2, 2, 0)
217 | previous, contains, evict := l.PeekOrAdd(1, 1, 0)
218 | if !contains {
219 | t.Errorf("1 should be contained")
220 | }
221 | if evict {
222 | t.Errorf("nothing should be evicted here")
223 | }
224 | if previous != 1 {
225 | t.Errorf("previous is not equal to 1")
226 | }
227 |
228 | l.Add(3, 3, 0)
229 | contains, evict = l.ContainsOrAdd(1, 1, 0)
230 | if contains {
231 | t.Errorf("1 should not have been contained")
232 | }
233 | if !evict {
234 | t.Errorf("an eviction should have occurred")
235 | }
236 | if !l.Contains(1) {
237 | t.Errorf("now 1 should be contained")
238 | }
239 | }
240 |
241 | // test that Peek doesn't update recent-ness
242 | func TestLRUPeek(t *testing.T) {
243 | l, err := NewLRU(2)
244 | if err != nil {
245 | t.Fatalf("err: %v", err)
246 | }
247 |
248 | l.Add(1, 1, 0)
249 | l.Add(2, 2, 0)
250 | if v, _, ok := l.Peek(1); !ok || v != 1 {
251 | t.Errorf("1 should be set to 1: %v, %v", v, ok)
252 | }
253 |
254 | l.Add(3, 3, 0)
255 | if l.Contains(1) {
256 | t.Errorf("should not have updated recent-ness of 1")
257 | }
258 | }
259 |
260 | // test that Resize can upsize and downsize
261 | func TestLRUResize(t *testing.T) {
262 | onEvictCounter := 0
263 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
264 | onEvictCounter++
265 | }
266 | l, err := NewLfuWithEvict(2, onEvicted)
267 | if err != nil {
268 | t.Fatalf("err: %v", err)
269 | }
270 |
271 | // Downsize
272 | l.Add(1, 1, 0)
273 | l.Add(2, 2, 0)
274 | evicted := l.Resize(1)
275 | if evicted != 1 {
276 | t.Errorf("1 element should have been evicted: %v", evicted)
277 | }
278 | if onEvictCounter != 1 {
279 | t.Errorf("onEvicted should have been called 1 time: %v", onEvictCounter)
280 | }
281 |
282 | l.Add(3, 3, 0)
283 | if l.Contains(1) {
284 | t.Errorf("Element 1 should have been evicted")
285 | }
286 |
287 | // Upsize
288 | evicted = l.Resize(2)
289 | if evicted != 0 {
290 | t.Errorf("0 elements should have been evicted: %v", evicted)
291 | }
292 |
293 | l.Add(4, 4, 0)
294 | if !l.Contains(3) || !l.Contains(4) {
295 | t.Errorf("lruCache should have contained 2 elements")
296 | }
297 | }
298 |
299 |
300 |
301 | // Hash 性能压测
302 | func TestLRU_Performance(t *testing.T) {
303 | //fmt.Println("runtime.NumCPU(): ", runtime.NumCPU())
304 | runtime.GOMAXPROCS(runtime.NumCPU())
305 |
306 | //cpu 性能分析 go tool pprof --pdf cpu ./cpu2.pprof > cpu.pdf
307 | //开始性能分析, 返回一个停止接口
308 | //stopper1 := profile.Start(profile.CPUProfile, profile.ProfilePath("."))
309 | //在main()结束时停止性能分析
310 | //defer stopper1.Stop()
311 |
312 | //// 查看导致阻塞同步的堆栈跟踪
313 | //stopper2 := profile.Start(profile.BlockProfile, profile.ProfilePath("."))
314 | //// 在main()结束时停止性能分析
315 | //defer stopper2.Stop()
316 | //
317 | //// 查看当前所有运行的 goroutines 堆栈跟踪
318 | //stopper3 := profile.Start(profile.GoroutineProfile, profile.ProfilePath("."))
319 | //// 在main()结束时停止性能分析
320 | //defer stopper3.Stop()
321 | //
322 | //// 查看当前所有运行的 goroutines 堆栈跟踪
323 | //stopper4 := profile.Start(profile.MemProfile, profile.ProfilePath("."))
324 | //// 在main()结束时停止性能分析
325 | //defer stopper4.Stop()
326 |
327 | count := 10000000
328 | l, _ := NewLRU(20000)
329 |
330 | wg := &sync.WaitGroup{}
331 | for k := 0; k < count; k++ {
332 | wg.Add(1)
333 | go LruPerformanceOne(l, wg, k)
334 | }
335 | wg.Wait()
336 | }
337 |
338 |
339 | func LruPerformanceOne(h *LruCache, c *sync.WaitGroup, k int) {
340 | for i := 0; i < 5; i++ {
341 |
342 | var strKey string
343 |
344 | strKey = strconv.Itoa(k) + "_" + strconv.Itoa(i)
345 | h.Add(strKey, &testJsonStr, 0)
346 | }
347 |
348 | // 通知main已经结束循环(我搞定了!)
349 | c.Done()
350 | }
351 |
--------------------------------------------------------------------------------
/simplelfu/lfu.go:
--------------------------------------------------------------------------------
1 | package simplelfu
2 |
3 | import (
4 | "container/list"
5 | "errors"
6 | "math"
7 | "time"
8 | )
9 | // EvictCallback is used to get a callback when a cache entry is evicted
10 | // EvictCallback 用于在缓存条目被淘汰时的回调函数
11 | type EvictCallback func(key interface{}, value interface{}, expirationTime int64)
12 |
13 | // LFU implements a non-thread safe fixed size LFU cache
14 | // LFU 实现一个非线程安全的固定大小的LFU缓存
15 | type LFU struct {
16 | size int
17 | evictList *list.List
18 | items map[interface{}]*list.Element
19 | onEvict EvictCallback
20 | }
21 |
22 | // entry is used to hold a value in the evictList
23 | // 缓存详细信息
24 | type entry struct {
25 | key interface{}
26 | value interface{}
27 | weight int64 // 访问次数
28 | expirationTime int64
29 | }
30 |
31 | // NewLFU constructs an LFU of the given size
32 | // NewLFU 构造一个给定大小的LFU
33 | func NewLFU(size int, onEvict EvictCallback) (*LFU, error) {
34 | if size <= 0 {
35 | return nil, errors.New("must provide a positive size")
36 | }
37 | c := &LFU{
38 | size: size,
39 | evictList: list.New(),
40 | items: make(map[interface{}]*list.Element),
41 | onEvict: onEvict,
42 | }
43 | return c, nil
44 | }
45 |
46 | // Purge is used to completely clear the cache.
47 | // Purge 用于完全清除缓存
48 | func (c *LFU) Purge() {
49 | for k, v := range c.items {
50 | if c.onEvict != nil {
51 | c.onEvict(k, v.Value.(*entry).value, v.Value.(*entry).expirationTime)
52 | }
53 | delete(c.items, k)
54 | }
55 | c.evictList.Init()
56 | }
57 |
58 | // PurgeOverdue is used to completely clear the overdue cache.
59 | // PurgeOverdue 清除过期缓存
60 | func (c *LFU) PurgeOverdue() {
61 | for _, ent := range c.items {
62 | // 判断此值是否已经超时,如果超时则进行删除
63 | if checkExpirationTime(ent.Value.(*entry).expirationTime) {
64 | c.removeElement(ent)
65 | }
66 | }
67 | c.evictList.Init()
68 | }
69 |
70 | // Add adds a value to the cache. Returns true if an eviction occurred.
71 | // Add 向缓存添加一个值。如果已经存在,则更新信息
72 | func (c *LFU) Add(key, value interface{}, expirationTime int64) (ok bool) {
73 | // 判断缓存中是否已经存在数据,如果已经存在则更新数据
74 | if ent, ok := c.items[key]; ok {
75 | ent.Value.(*entry).value = value
76 | ent.Value.(*entry).expirationTime = expirationTime
77 | ent.Value.(*entry).weight++
78 | // 判断前一个元素 weight 值是否小于当前元素, 如果小于则替换顺序
79 | if (ent.Prev() != nil) && (ent.Prev().Value.(*entry).weight < ent.Value.(*entry).weight) {
80 | c.evictList.MoveBefore(ent, ent.Prev())
81 | }
82 | return true
83 | }
84 | // 判断缓存条数是否已经达到限制
85 | if c.evictList.Len() >= c.size {
86 | c.removeOldest()
87 | }
88 | // 创建数据
89 | ent := &entry{key, value, 1, expirationTime}
90 | c.items[key] = c.evictList.PushBack(ent)
91 |
92 | return true
93 | }
94 |
95 | // Get looks up a key's value from the cache.
96 | // Get 从缓存中查找一个键的值。
97 | func (c *LFU) Get(key interface{}) (value interface{}, expirationTime int64, ok bool) {
98 | // 判断缓存是否存在
99 | if ent, ok := c.items[key]; ok {
100 | // 判断此值是否已经超时,如果超时则进行删除
101 | if checkExpirationTime(ent.Value.(*entry).expirationTime) {
102 | c.removeElement(ent)
103 | return nil, 0, false
104 | }
105 | ent.Value.(*entry).weight++
106 | // 判断前一个元素 weight 值是否小于当前元素, 如果小于则替换顺序
107 | if (ent.Prev() != nil) && (ent.Prev().Value.(*entry).weight < ent.Value.(*entry).weight) {
108 | c.evictList.MoveBefore(ent, ent.Prev())
109 | }
110 | return ent.Value.(*entry).value, ent.Value.(*entry).expirationTime, true
111 | }
112 | return nil, 0, false
113 | }
114 |
115 | // Contains checks if a key is in the cache, without updating the recent-ness
116 | // or deleting it for being stale.
117 | // Contains 检查某个键是否在缓存中,但不更新缓存的状态
118 | func (c *LFU) Contains(key interface{}) (ok bool) {
119 | ent, ok := c.items[key]
120 | if ok {
121 | // 判断此值是否已经超时,如果超时则进行删除
122 | if checkExpirationTime(ent.Value.(*entry).expirationTime) {
123 | c.removeElement(ent)
124 | return !ok
125 | }
126 | }
127 | return ok
128 | }
129 |
130 | // Peek returns the key value (or undefined if not found) without updating
131 | // the "recently used"-ness of the key.
132 | // Peek 在不更新的情况下返回键值(如果没有找到则返回false),不更新缓存的状态
133 | func (c *LFU) Peek(key interface{}) (value interface{}, expirationTime int64, ok bool) {
134 | var ent *list.Element
135 | if ent, ok = c.items[key]; ok {
136 | // 判断是否已经超时
137 | if checkExpirationTime(ent.Value.(*entry).expirationTime) {
138 | c.removeElement(ent)
139 | return nil, 0, ok
140 | }
141 | return ent.Value.(*entry).value, ent.Value.(*entry).expirationTime, true
142 | }
143 | return nil, 0, ok
144 | }
145 |
146 | // Remove removes the provided key from the cache, returning if the
147 | // key was contained.
148 | // Remove 从缓存中移除提供的键
149 | func (c *LFU) Remove(key interface{}) (ok bool) {
150 | if ent, ok := c.items[key]; ok {
151 | c.removeElement(ent)
152 | return ok
153 | }
154 | return ok
155 | }
156 |
157 | // RemoveOldest removes the oldest item from the cache.
158 | // RemoveOldest 从缓存中移除最老的项
159 | func (c *LFU) RemoveOldest() (key interface{}, value interface{}, expirationTime int64, ok bool) {
160 | if ent := c.evictList.Back(); ent != nil {
161 | // 判断是否已经超时
162 | if checkExpirationTime(ent.Value.(*entry).expirationTime) {
163 | c.removeElement(ent)
164 | return c.RemoveOldest()
165 | }
166 | c.removeElement(ent)
167 |
168 | return ent.Value.(*entry).key, ent.Value.(*entry).value, ent.Value.(*entry).expirationTime, true
169 | }
170 | return nil, nil, 0, false
171 | }
172 |
173 | // GetOldest returns the oldest entry
174 | // GetOldest 返回最老的条目
175 | func (c *LFU) GetOldest() (key interface{}, value interface{}, expirationTime int64, ok bool) {
176 | ent := c.evictList.Back()
177 | if ent != nil {
178 | // 判断此值是否已经超时,如果超时则进行删除
179 | if checkExpirationTime(ent.Value.(*entry).expirationTime) {
180 | c.removeElement(ent)
181 | return c.GetOldest()
182 | }
183 | // 引用自增
184 | ent.Value.(*entry).weight++
185 | return ent.Value.(*entry).key, ent.Value.(*entry).value, ent.Value.(*entry).expirationTime, true
186 | }
187 | return nil, nil, 0, false
188 | }
189 |
190 | // Keys returns a slice of the keys in the cache, from oldest to newest.
191 | // Keys 返回缓存的切片,从最老的到最新的。
192 | func (c *LFU) Keys() []interface{} {
193 | keys := make([]interface{}, len(c.items))
194 | i := 0
195 | for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() {
196 | keys[i] = ent.Value.(*entry).key
197 | i++
198 | }
199 | return keys
200 | }
201 |
202 | // Len returns the number of items in the cache.
203 | // Len 返回缓存中的条数
204 | func (c *LFU) Len() int {
205 | return c.evictList.Len()
206 | }
207 |
208 | // Resize changes the cache size.
209 | // Resize 改变缓存大小。
210 | func (c *LFU) Resize(size int) (evicted int) {
211 | diff := c.Len() - size
212 | if diff < 0 {
213 | diff = 0
214 | }
215 | for i := 0; i < diff; i++ {
216 | c.removeOldest()
217 | }
218 | c.size = size
219 | return diff
220 | }
221 |
222 | // ResizeWeight changes the cache eight weight size.
223 | // ResizeWeight 改变缓存中Weight大小。
224 | func (c *LFU) ResizeWeight(percentage int) {
225 | if percentage > 0 || percentage < 100 {
226 | for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() {
227 | ent.Value.(*entry).weight = int64(math.Ceil(float64(ent.Value.(*entry).weight / 100 * int64(percentage))))
228 | }
229 | }
230 | }
231 |
232 | // removeOldest removes the oldest item from the cache.
233 | // removeOldest 从缓存中移除最老的项。
234 | func (c *LFU) removeOldest() {
235 | ent := c.evictList.Back()
236 | if ent != nil {
237 | c.removeElement(ent)
238 | }
239 | }
240 |
241 | // removeElement is used to remove a given list element from the cache
242 | // removeElement 从缓存中移除一个列表元素
243 | func (c *LFU) removeElement(e *list.Element) {
244 | c.evictList.Remove(e)
245 | delete(c.items, e.Value.(*entry).key)
246 | if c.onEvict != nil {
247 | c.onEvict(e.Value.(*entry).key, e.Value.(*entry).value, e.Value.(*entry).expirationTime)
248 | }
249 | }
250 |
251 | // checkExpirationTime is Determine if the cache has expired
252 | // checkExpirationTime 判断缓存是否已经过期
253 | func checkExpirationTime(expirationTime int64) (ok bool) {
254 | if 0 != expirationTime && expirationTime <= time.Now().UnixNano()/1e6 {
255 | return true
256 | }
257 | return false
258 | }
259 |
--------------------------------------------------------------------------------
/simplelfu/lfu_interface.go:
--------------------------------------------------------------------------------
1 | package simplelfu
2 |
3 | // LFUCache 是简单LFU缓存的接口。
4 | type LFUCache interface {
5 |
6 | // Add 向缓存添加一个值。如果已经存在,则更新信息
7 | Add(key, value interface{}, expirationTime int64) (ok bool)
8 |
9 | // Get 从缓存中查找一个键的值。
10 | Get(key interface{}) (value interface{}, expirationTime int64, ok bool)
11 |
12 | // Contains 检查某个键是否在缓存中,但不更新缓存的状态
13 | Contains(key interface{}) (ok bool)
14 |
15 | // Peek 在不更新的情况下返回键值(如果没有找到则返回false),不更新缓存的状态
16 | Peek(key interface{}) (value interface{}, expirationTime int64, ok bool)
17 |
18 | // Remove 从缓存中移除提供的键。
19 | Remove(key interface{}) (ok bool)
20 |
21 | // RemoveOldest 从缓存中移除最老的项
22 | RemoveOldest() (key interface{}, value interface{}, expirationTime int64, ok bool)
23 |
24 | // GetOldest 从缓存中返回最旧的条目
25 | GetOldest() (key interface{}, value interface{}, expirationTime int64, ok bool)
26 |
27 | // Keys 返回缓存中键的切片,从最老到最新
28 | Keys() []interface{}
29 |
30 | // Len 获取缓存已存在的缓存条数
31 | Len() int
32 |
33 | // Purge 清除所有缓存项
34 | Purge()
35 |
36 | // PurgeOverdue 清除所有过期缓存项。
37 | PurgeOverdue()
38 |
39 | // Resize 调整缓存大小,返回调整前的数量
40 | Resize(int) int
41 |
42 | // ResizeWeight 调整缓存中weight引用次数
43 | ResizeWeight(int)
44 | }
45 |
--------------------------------------------------------------------------------
/simplelfu/lfu_test.go:
--------------------------------------------------------------------------------
1 | package simplelfu
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | "time"
7 | )
8 |
9 | func TestLFU(t *testing.T) {
10 |
11 | initTime := initTime()
12 | evictCounter := 0
13 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
14 | if k != v {
15 | t.Fatalf("Evict values not equal (%v!=%v) , time = %v", k, v, expirationTime)
16 | }
17 | evictCounter++
18 | }
19 | l, err := NewLFU(128, onEvicted)
20 | if err != nil {
21 | t.Fatalf("err: %v", err)
22 | }
23 |
24 | for i := 0; i < 256; i++ {
25 | l.Add(i, i, initTime)
26 | for c := 0; c < i; c++ {
27 | l.Get(i)
28 | }
29 | }
30 |
31 | if l.Len() != 128 {
32 | t.Fatalf("bad len: %v", l.Len())
33 | }
34 |
35 | if evictCounter != 128 {
36 | t.Fatalf("bad evict count: %v", evictCounter)
37 | }
38 |
39 | for i, k := range l.Keys() {
40 | if v, expirationTime, ok := l.Get(k); !ok || v != k || v != i+128 {
41 | t.Fatalf("bad i: %v, key: %v, v: %v, time: %v", i, k, v, expirationTime)
42 | }
43 | }
44 | for i := 0; i < 128; i++ {
45 | _, expirationTime, ok := l.Get(i)
46 | if ok {
47 | t.Fatalf("should be evicted , time: %v", expirationTime)
48 | }
49 | }
50 | for i := 128; i < 256; i++ {
51 | _, expirationTime, ok := l.Get(i)
52 | if !ok {
53 | t.Fatalf("should not be evicted, time: %v", expirationTime)
54 | }
55 | }
56 |
57 | for i := 128; i < 192; i++ {
58 | ok := l.Remove(i)
59 | if !ok {
60 | t.Fatalf("should be contained")
61 | }
62 | ok = l.Remove(i)
63 | if ok {
64 | t.Fatalf("should not be contained")
65 | }
66 | _, expirationTime, ok := l.Get(i)
67 | if ok {
68 | t.Fatalf("should be deleted, time: %v", expirationTime)
69 | }
70 | }
71 |
72 | l.Get(192) // expect 192 to be first key in l.Keys()
73 | for i, k := range l.Keys() {
74 | if i < 63 && k != 192+i {
75 | t.Fatalf("out of order i:% v ,key: %v", i, k)
76 | }
77 | }
78 |
79 | l.Purge()
80 | if l.Len() != 0 {
81 | t.Fatalf("bad len: %v", l.Len())
82 | }
83 | if _, expirationTime, ok := l.Get(200); ok {
84 | t.Fatalf("should contain nothing, time: %v", expirationTime)
85 | }
86 | }
87 |
88 | func TestLFU_GetOldest_RemoveOldest(t *testing.T) {
89 | initTime := initTime()
90 |
91 | l, err := NewLFU(128, nil)
92 | if err != nil {
93 | t.Fatalf("err: %v", err)
94 | }
95 | for i := 0; i < 256; i++ {
96 | l.Add(i, i, initTime)
97 | for c := 0; c < i; c++ {
98 | l.Get(i)
99 | }
100 | }
101 | k, _, _, ok := l.GetOldest()
102 | if !ok {
103 | t.Fatalf("missing")
104 | }
105 | if k.(int) != 128 {
106 | t.Fatalf("bad: %v", k)
107 | }
108 |
109 | k, _, _, ok = l.RemoveOldest()
110 | if !ok {
111 | t.Fatalf("missing")
112 | }
113 | if k.(int) != 128 {
114 | t.Fatalf("bad: %v", k)
115 | }
116 |
117 | k, _, _, ok = l.RemoveOldest()
118 | if !ok {
119 | t.Fatalf("missing")
120 | }
121 | if k.(int) != 129 {
122 | t.Fatalf("bad: %v", k)
123 | }
124 | }
125 |
126 | // Test that Add returns true/false if an eviction occurred
127 | func TestLFU_Add(t *testing.T) {
128 | initTime := initTime()
129 |
130 | evictCounter := 0
131 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
132 | evictCounter++
133 | }
134 |
135 | l, err := NewLFU(1, onEvicted)
136 | if err != nil {
137 | t.Fatalf("err: %v", err)
138 | }
139 |
140 | if l.Add(1, 1, initTime) == false || evictCounter != 0 {
141 | t.Errorf(fmt.Sprint(evictCounter))
142 | t.Errorf("should not have an eviction")
143 | }
144 |
145 | if l.Add(2, 2, initTime) == false || evictCounter != 1 {
146 | t.Errorf(fmt.Sprint(evictCounter))
147 | t.Errorf("should have an eviction")
148 | }
149 | }
150 |
151 | // Test that Contains doesn't update recent-ness
152 | func TestLFU_Contains(t *testing.T) {
153 | initTime := initTime()
154 |
155 | l, err := NewLFU(2, nil)
156 | if err != nil {
157 | t.Fatalf("err: %v", err)
158 | }
159 |
160 | l.Add(1, 1, initTime)
161 | l.Get(1)
162 | l.Add(2, 2, initTime)
163 | l.Get(2)
164 | l.Get(2)
165 | if !l.Contains(1) {
166 | t.Errorf("1 should be contained")
167 | }
168 |
169 | l.Add(3, 3, initTime)
170 | if l.Contains(1) {
171 | t.Errorf("Contains should not have updated recent-ness of 1")
172 | }
173 | }
174 |
175 | // Test that Peek doesn't update recent-ness
176 | func TestLFU_Peek(t *testing.T) {
177 | initTime := initTime()
178 |
179 | l, err := NewLFU(2, nil)
180 | if err != nil {
181 | t.Fatalf("err: %v", err)
182 | }
183 |
184 | l.Add(1, 1, initTime)
185 | l.Get(1)
186 | l.Add(2, 2, initTime)
187 | l.Get(2)
188 | l.Get(2)
189 | if v, _, ok := l.Peek(1); !ok || v != 1 {
190 | t.Errorf("1 should be set to 1: %v, %v", v, ok)
191 | }
192 |
193 | l.Add(3, 3, initTime)
194 | if l.Contains(1) {
195 | t.Errorf("should not have updated recent-ness of 1")
196 | }
197 | }
198 |
199 | // Test that Resize can upsize and downsize
200 | func TestLFU_Resize(t *testing.T) {
201 | initTime := initTime()
202 |
203 | onEvictCounter := 0
204 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
205 | onEvictCounter++
206 | }
207 | l, err := NewLFU(2, onEvicted)
208 | if err != nil {
209 | t.Fatalf("err: %v", err)
210 | }
211 |
212 | // Downsize
213 | l.Add(1, 1, initTime)
214 | l.Add(2, 2, initTime)
215 | evicted := l.Resize(1)
216 | if evicted != 1 {
217 | t.Errorf("1 element should have been evicted: %v", evicted)
218 | }
219 | if onEvictCounter != 1 {
220 | t.Errorf("onEvicted should have been called 1 time: %v", onEvictCounter)
221 | }
222 |
223 | l.Add(3, 3, initTime)
224 | if l.Contains(1) {
225 | t.Errorf("Element 1 should have been evicted")
226 | }
227 |
228 | // Upsize
229 | evicted = l.Resize(2)
230 | if evicted != 0 {
231 | t.Errorf("0 elements should have been evicted: %v", evicted)
232 | }
233 |
234 | l.Add(4, 4, initTime)
235 | if !l.Contains(3) || !l.Contains(4) {
236 | t.Errorf("Cache should have contained 2 elements")
237 | }
238 | }
239 |
240 | // 生成当前时间 + 2秒
241 | func initTime() int64 {
242 | return time.Now().UnixNano()/1e6 + 2000
243 | }
244 |
--------------------------------------------------------------------------------
/simplelru/lru.go:
--------------------------------------------------------------------------------
1 | package simplelru
2 |
3 | import (
4 | "container/list"
5 | "errors"
6 | "time"
7 | )
8 | // EvictCallback is used to get a callback when a cache entry is evicted
9 | // EvictCallback 用于在缓存条目被淘汰时的回调函数
10 | type EvictCallback func(key interface{}, value interface{}, expirationTime int64)
11 |
12 | // LRU implements a non-thread safe fixed size LRU cache
13 | // LRU 实现一个非线程安全的固定大小的LRU缓存
14 | type LRU struct {
15 | size int
16 | evictList *list.List
17 | items map[interface{}]*list.Element
18 | onEvict EvictCallback
19 | }
20 |
21 | // entry is used to hold a value in the evictList
22 | // 缓存详细信息
23 | type entry struct {
24 | key interface{}
25 | value interface{}
26 | expirationTime int64
27 | }
28 |
29 | // NewLRU constructs an LRU of the given size
30 | // NewLRU 构造一个给定大小的LRU
31 | func NewLRU(size int, onEvict EvictCallback) (*LRU, error) {
32 | if size <= 0 {
33 | return nil, errors.New("must provide a positive size")
34 | }
35 | c := &LRU{
36 | size: size,
37 | evictList: list.New(),
38 | items: make(map[interface{}]*list.Element),
39 | onEvict: onEvict,
40 | }
41 | return c, nil
42 | }
43 |
44 | // Purge is used to completely clear the cache.
45 | // Purge 用于完全清除缓存
46 | func (c *LRU) Purge() {
47 | for k, v := range c.items {
48 | if c.onEvict != nil {
49 | c.onEvict(k, v.Value.(*entry).value, v.Value.(*entry).expirationTime)
50 | }
51 | delete(c.items, k)
52 | }
53 | c.evictList.Init()
54 | }
55 |
56 | // PurgeOverdue is used to completely clear the overdue cache.
57 | // PurgeOverdue 清除过期缓存
58 | func (c *LRU) PurgeOverdue() {
59 | for _, ent := range c.items {
60 | // 判断此值是否已经超时,如果超时则进行删除
61 | if checkExpirationTime(ent.Value.(*entry).expirationTime) {
62 | c.removeElement(ent)
63 | }
64 | }
65 | c.evictList.Init()
66 | }
67 |
68 | // Add adds a value to the cache. Returns true if an eviction occurred.
69 | // Add 向缓存添加一个值。如果已经存在,则更新信息
70 | func (c *LRU) Add(key, value interface{}, expirationTime int64) (ok bool) {
71 | // 判断缓存中是否已经存在数据,如果已经存在则更新数据
72 | if ent, ok := c.items[key]; ok {
73 | c.evictList.MoveToFront(ent)
74 | ent.Value.(*entry).value = value
75 | ent.Value.(*entry).expirationTime = expirationTime
76 | return true
77 | }
78 | // 判断缓存条数是否已经达到限制
79 | if c.evictList.Len() >= c.size {
80 | c.removeOldest()
81 | }
82 | // 创建数据
83 | ent := &entry{key, value, expirationTime}
84 |
85 | c.items[key] = c.evictList.PushFront(ent)
86 | return true
87 | }
88 |
89 | // Get looks up a key's value from the cache.
90 | // Get 从缓存中查找一个键的值。
91 | func (c *LRU) Get(key interface{}) (value interface{}, expirationTime int64, ok bool) {
92 | // 判断缓存是否存在
93 | if ent, ok := c.items[key]; ok {
94 | // 判断此值是否已经超时,如果超时则进行删除
95 | if checkExpirationTime(ent.Value.(*entry).expirationTime) {
96 | c.removeElement(ent)
97 | return nil, 0, false
98 | }
99 | // 数据移到头部
100 | c.evictList.MoveToFront(ent)
101 | return ent.Value.(*entry).value, ent.Value.(*entry).expirationTime, true
102 | }
103 | return nil, 0, false
104 | }
105 |
106 | // Contains checks if a key is in the cache, without updating the recent-ness
107 | // or deleting it for being stale.
108 | // Contains 检查某个键是否在缓存中,但不更新缓存的状态
109 | func (c *LRU) Contains(key interface{}) (ok bool) {
110 | ent, ok := c.items[key]
111 | if ok {
112 | // 判断此值是否已经超时,如果超时则进行删除
113 | if checkExpirationTime(ent.Value.(*entry).expirationTime) {
114 | c.removeElement(ent)
115 | return !ok
116 | }
117 | }
118 | return ok
119 | }
120 |
121 | // Peek returns the key value (or undefined if not found) without updating
122 | // the "recently used"-ness of the key.
123 | // Peek 在不更新的情况下返回键值(如果没有找到则返回false),不更新缓存的状态
124 | func (c *LRU) Peek(key interface{}) (value interface{}, expirationTime int64, ok bool) {
125 | var ent *list.Element
126 | if ent, ok = c.items[key]; ok {
127 | // 判断是否已经超时
128 | if checkExpirationTime(ent.Value.(*entry).expirationTime) {
129 | c.removeElement(ent)
130 | return nil, 0, ok
131 | }
132 | return ent.Value.(*entry).value, ent.Value.(*entry).expirationTime, true
133 | }
134 | return nil, 0, ok
135 | }
136 |
137 | // Remove removes the provided key from the cache, returning if the
138 | // key was contained.
139 | // Remove 从缓存中移除提供的键
140 | func (c *LRU) Remove(key interface{}) (ok bool) {
141 | if ent, ok := c.items[key]; ok {
142 | c.removeElement(ent)
143 | return ok
144 | }
145 | return ok
146 | }
147 |
148 | // RemoveOldest removes the oldest item from the cache.
149 | // RemoveOldest 从缓存中移除最老的项
150 | func (c *LRU) RemoveOldest() (key interface{}, value interface{}, expirationTime int64, ok bool) {
151 | if ent := c.evictList.Back(); ent != nil {
152 | // 判断是否已经超时
153 | if checkExpirationTime(ent.Value.(*entry).expirationTime) {
154 | c.removeElement(ent)
155 | return c.RemoveOldest()
156 | }
157 |
158 | c.removeElement(ent)
159 | return ent.Value.(*entry).key, ent.Value.(*entry).value, ent.Value.(*entry).expirationTime, true
160 | }
161 | return nil, nil, 0, false
162 | }
163 |
164 | // GetOldest returns the oldest entry
165 | // GetOldest 返回最老的条目
166 | func (c *LRU) GetOldest() (key interface{}, value interface{}, expirationTime int64, ok bool) {
167 | if ent := c.evictList.Back(); ent != nil {
168 | // 判断此值是否已经超时,如果超时则进行删除
169 | if checkExpirationTime(ent.Value.(*entry).expirationTime) {
170 | c.removeElement(ent)
171 | return c.GetOldest()
172 | }
173 | return ent.Value.(*entry).key, ent.Value.(*entry).value, ent.Value.(*entry).expirationTime, true
174 | }
175 | return nil, nil, 0, false
176 | }
177 |
178 | // Keys returns a slice of the keys in the cache, from oldest to newest.
179 | // Keys 返回缓存的切片,从最老的到最新的。
180 | func (c *LRU) Keys() []interface{} {
181 | keys := make([]interface{}, len(c.items))
182 | i := 0
183 | for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() {
184 | keys[i] = ent.Value.(*entry).key
185 | i++
186 | }
187 | return keys
188 | }
189 |
190 | // Len returns the number of items in the cache.
191 | // Len 返回缓存中的条数
192 | func (c *LRU) Len() int {
193 | return c.evictList.Len()
194 | }
195 |
196 | // Resize changes the cache size.
197 | // Resize 改变缓存大小。
198 | func (c *LRU) Resize(size int) (evicted int) {
199 | diff := c.Len() - size
200 | if diff < 0 {
201 | diff = 0
202 | }
203 | for i := 0; i < diff; i++ {
204 | c.removeOldest()
205 | }
206 | c.size = size
207 | return diff
208 | }
209 |
210 | // removeOldest removes the oldest item from the cache.
211 | // removeOldest 从缓存中移除最老的项。
212 | func (c *LRU) removeOldest() {
213 | ent := c.evictList.Back()
214 | if ent != nil {
215 | c.removeElement(ent)
216 | }
217 | }
218 |
219 | // removeElement is used to remove a given list element from the cache
220 | // removeElement 从缓存中移除一个列表元素
221 | func (c *LRU) removeElement(e *list.Element) {
222 | c.evictList.Remove(e)
223 | delete(c.items, e.Value.(*entry).key)
224 | if c.onEvict != nil {
225 | c.onEvict(e.Value.(*entry).key, e.Value.(*entry).value, e.Value.(*entry).expirationTime)
226 | }
227 | }
228 |
229 | // checkExpirationTime is Determine if the cache has expired
230 | // checkExpirationTime 判断缓存是否已经过期
231 | func checkExpirationTime(expirationTime int64) (ok bool) {
232 | if 0 != expirationTime && expirationTime <= time.Now().UnixNano()/1e6 {
233 | return true
234 | }
235 | return false
236 | }
237 |
--------------------------------------------------------------------------------
/simplelru/lru_interface.go:
--------------------------------------------------------------------------------
1 | package simplelru
2 |
3 | // LRUCache 是简单LRU缓存的接口。
4 | type LRUCache interface {
5 |
6 | // Add 向缓存添加一个值。如果已经存在,则更新信息
7 | Add(key, value interface{}, expirationTime int64) (ok bool)
8 |
9 | // Get 从缓存中查找一个键的值。
10 | Get(key interface{}) (value interface{}, expirationTime int64, ok bool)
11 |
12 | // Contains 检查某个键是否在缓存中,但不更新缓存的状态
13 | Contains(key interface{}) (ok bool)
14 |
15 | // Peek 在不更新的情况下返回键值(如果没有找到则返回false),不更新缓存的状态
16 | Peek(key interface{}) (value interface{}, expirationTime int64, ok bool)
17 |
18 | // Remove 从缓存中移除提供的键。
19 | Remove(key interface{}) (ok bool)
20 |
21 | // RemoveOldest 从缓存中移除最老的项
22 | RemoveOldest() (key interface{}, value interface{}, expirationTime int64, ok bool)
23 |
24 | // GetOldest 从缓存中返回最旧的条目
25 | GetOldest() (key interface{}, value interface{}, expirationTime int64, ok bool)
26 |
27 | // Keys 返回缓存中键的切片,从最老到最新
28 | Keys() []interface{}
29 |
30 | // Len 获取缓存已存在的缓存条数
31 | Len() int
32 |
33 | // Purge 清除所有缓存项
34 | Purge()
35 |
36 | // PurgeOverdue 清除所有过期缓存项。
37 | PurgeOverdue()
38 |
39 | // Resize 调整缓存大小,返回调整前的数量
40 | Resize(int) int
41 | }
42 |
--------------------------------------------------------------------------------
/simplelru/lru_test.go:
--------------------------------------------------------------------------------
1 | package simplelru
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | "time"
7 | )
8 |
9 | func TestLRU(t *testing.T) {
10 |
11 | initTime := initTime()
12 | evictCounter := 0
13 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
14 | if k != v {
15 | t.Fatalf("Evict values not equal (%v!=%v) , time = %v", k, v, expirationTime)
16 | }
17 | evictCounter++
18 | }
19 | l, err := NewLRU(128, onEvicted)
20 | if err != nil {
21 | t.Fatalf("err: %v", err)
22 | }
23 |
24 | for i := 0; i < 256; i++ {
25 | l.Add(i, i, initTime)
26 | }
27 | if l.Len() != 128 {
28 | t.Fatalf("bad len: %v", l.Len())
29 | }
30 |
31 | if evictCounter != 128 {
32 | t.Fatalf("bad evict count: %v", evictCounter)
33 | }
34 |
35 | for i, k := range l.Keys() {
36 | if v, expirationTime, ok := l.Get(k); !ok || v != k || v != i+128 {
37 | t.Fatalf("bad key: %v, time: %v", k, expirationTime)
38 | }
39 | }
40 | for i := 0; i < 128; i++ {
41 | _, expirationTime, ok := l.Get(i)
42 | if ok {
43 | t.Fatalf("should be evicted , time: %v", expirationTime)
44 | }
45 | }
46 | for i := 128; i < 256; i++ {
47 | _, expirationTime, ok := l.Get(i)
48 | if !ok {
49 | t.Fatalf("should not be evicted, time: %v", expirationTime)
50 | }
51 | }
52 | for i := 128; i < 192; i++ {
53 | ok := l.Remove(i)
54 | if !ok {
55 | t.Fatalf("should be contained")
56 | }
57 | ok = l.Remove(i)
58 | if ok {
59 | t.Fatalf("should not be contained")
60 | }
61 | _, expirationTime, ok := l.Get(i)
62 | if ok {
63 | t.Fatalf("should be deleted, time: %v", expirationTime)
64 | }
65 | }
66 |
67 | l.Get(192) // expect 192 to be last key in l.Keys()
68 |
69 | for i, k := range l.Keys() {
70 | if (i < 63 && k != i+193) || (i == 63 && k != 192) {
71 | t.Fatalf("out of order key: %v", k)
72 | }
73 | }
74 |
75 | l.Purge()
76 | if l.Len() != 0 {
77 | t.Fatalf("bad len: %v", l.Len())
78 | }
79 | if _, expirationTime, ok := l.Get(200); ok {
80 | t.Fatalf("should contain nothing, time: %v", expirationTime)
81 | }
82 | }
83 |
84 | func TestLRU_GetOldest_RemoveOldest(t *testing.T) {
85 | initTime := initTime()
86 |
87 | l, err := NewLRU(128, nil)
88 | if err != nil {
89 | t.Fatalf("err: %v", err)
90 | }
91 | for i := 0; i < 256; i++ {
92 | l.Add(i, i, initTime)
93 | }
94 | k, _, _, ok := l.GetOldest()
95 | if !ok {
96 | t.Fatalf("missing")
97 | }
98 | if k.(int) != 128 {
99 | t.Fatalf("bad: %v", k)
100 | }
101 |
102 | k, _, _, ok = l.RemoveOldest()
103 | if !ok {
104 | t.Fatalf("missing")
105 | }
106 | if k.(int) != 128 {
107 | t.Fatalf("bad: %v", k)
108 | }
109 |
110 | k, _, _, ok = l.RemoveOldest()
111 | if !ok {
112 | t.Fatalf("missing")
113 | }
114 | if k.(int) != 129 {
115 | t.Fatalf("bad: %v", k)
116 | }
117 | }
118 |
119 | // Test that Add returns true/false if an eviction occurred
120 | func TestLRU_Add(t *testing.T) {
121 | initTime := initTime()
122 |
123 | evictCounter := 0
124 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
125 | evictCounter++
126 | }
127 |
128 | l, err := NewLRU(1, onEvicted)
129 | if err != nil {
130 | t.Fatalf("err: %v", err)
131 | }
132 |
133 | if l.Add(1, 1, initTime) == false || evictCounter != 0 {
134 | t.Errorf(fmt.Sprint(evictCounter))
135 | t.Errorf("should not have an eviction")
136 | }
137 |
138 | if l.Add(2, 2, initTime) == false || evictCounter != 1 {
139 | t.Errorf(fmt.Sprint(evictCounter))
140 | t.Errorf("should have an eviction")
141 | }
142 | }
143 |
144 | // Test that Contains doesn't update recent-ness
145 | func TestLRU_Contains(t *testing.T) {
146 | initTime := initTime()
147 |
148 | l, err := NewLRU(2, nil)
149 | if err != nil {
150 | t.Fatalf("err: %v", err)
151 | }
152 |
153 | l.Add(1, 1, initTime)
154 | l.Add(2, 2, initTime)
155 | if !l.Contains(1) {
156 | t.Errorf("1 should be contained")
157 | }
158 |
159 | l.Add(3, 3, initTime)
160 | if l.Contains(1) {
161 | t.Errorf("Contains should not have updated recent-ness of 1")
162 | }
163 | }
164 |
165 | // Test that Peek doesn't update recent-ness
166 | func TestLRU_Peek(t *testing.T) {
167 | initTime := initTime()
168 |
169 | l, err := NewLRU(2, nil)
170 | if err != nil {
171 | t.Fatalf("err: %v", err)
172 | }
173 |
174 | l.Add(1, 1, initTime)
175 | l.Add(2, 2, initTime)
176 | if v, _, ok := l.Peek(1); !ok || v != 1 {
177 | t.Errorf("1 should be set to 1: %v, %v", v, ok)
178 | }
179 |
180 | l.Add(3, 3, initTime)
181 | if l.Contains(1) {
182 | t.Errorf("should not have updated recent-ness of 1")
183 | }
184 | }
185 |
186 | // Test that Resize can upsize and downsize
187 | func TestLRU_Resize(t *testing.T) {
188 | initTime := initTime()
189 |
190 | onEvictCounter := 0
191 | onEvicted := func(k interface{}, v interface{}, expirationTime int64) {
192 | onEvictCounter++
193 | }
194 | l, err := NewLRU(2, onEvicted)
195 | if err != nil {
196 | t.Fatalf("err: %v", err)
197 | }
198 |
199 | // Downsize
200 | l.Add(1, 1, initTime)
201 | l.Add(2, 2, initTime)
202 | evicted := l.Resize(1)
203 | if evicted != 1 {
204 | t.Errorf("1 element should have been evicted: %v", evicted)
205 | }
206 | if onEvictCounter != 1 {
207 | t.Errorf("onEvicted should have been called 1 time: %v", onEvictCounter)
208 | }
209 |
210 | l.Add(3, 3, initTime)
211 | if l.Contains(1) {
212 | t.Errorf("Element 1 should have been evicted")
213 | }
214 |
215 | // Upsize
216 | evicted = l.Resize(2)
217 | if evicted != 0 {
218 | t.Errorf("0 elements should have been evicted: %v", evicted)
219 | }
220 |
221 | l.Add(4, 4, initTime)
222 | if !l.Contains(3) || !l.Contains(4) {
223 | t.Errorf("Cache should have contained 2 elements")
224 | }
225 | }
226 |
227 | // 生成当前时间
228 | func initTime() int64 {
229 | return time.Now().UnixNano()/1e6 + 2000
230 | }
231 |
--------------------------------------------------------------------------------