├── .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 | --------------------------------------------------------------------------------