├── .travis.yml ├── LICENSE ├── README.md ├── arc.go ├── arc_test.go ├── entry.go └── util.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - tip 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Hans Alexander Gugel 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/alexanderGugel/arc.svg?branch=master)](https://travis-ci.org/alexanderGugel/arc) 2 | 3 | **NEW** Looking for an ARC in Javascript? - [alexanderGugel/arc-js](https://github.com/alexanderGugel/arc-js) 4 | 5 | arc 6 | === 7 | 8 | An [Adaptive Replacement Cache (ARC)](http://web.archive.org/web/20150405221102/https://www.usenix.org/legacy/event/fast03/tech/full_papers/megiddo/megiddo.pdf) written in [Go](http://golang.org/). 9 | 10 | [GoDoc](https://godoc.org/github.com/alexanderGugel/arc) 11 | 12 | This project implements "ARC", a self-tuning, low overhead replacement cache. The goal of this project is to expose an interface compareable to common LRU cache management systems. ARC uses a learning rule to adaptively and continually revise its assumptions about the workload in order to adjust the internal LRU and LFU cache sizes. 13 | 14 | This implementation is based on Nimrod Megiddo and Dharmendra S. Modha's ["ARC: A SELF-TUNING, LOW OVERHEAD REPLACEMENT CACHE"](http://web.archive.org/web/20150405221102/https://www.usenix.org/legacy/event/fast03/tech/full_papers/megiddo/megiddo.pdf), while definitely useable and thread safe, this is still an experiment and shouldn't be considered production-ready. 15 | 16 | ``` 17 | <------- cache size c ------> 18 | +-----------------+---------- 19 | | LFU | LRU | 20 | +-----------------+---------- 21 | ^ 22 | | 23 | p (dynamically adjusted by learning rule) 24 | 25 | B1 [...] 26 | B2 [...] 27 | ``` 28 | 29 | The cache is implemented using two internal caching systems L1 and L2. The cache size c defines the maximum number of entries stored (excluding ghost entries). Ghost entries are being stored in two "ghost registries" B1 and B1. Ghost entries no longer have a value associated with them. 30 | 31 | Ghost entries are being used in order to keep track of expelled pages. They no longer have a value associated with them, but can be promoted into the internal LRU cache. 32 | 33 | Frequently requested pages are being promoted into the LFU. 34 | -------------------------------------------------------------------------------- /arc.go: -------------------------------------------------------------------------------- 1 | // Package arc implements an Adaptive replacement cache 2 | package arc 3 | 4 | import ( 5 | "container/list" 6 | "sync" 7 | ) 8 | 9 | type ARC struct { 10 | p int 11 | c int 12 | t1 *list.List 13 | b1 *list.List 14 | t2 *list.List 15 | b2 *list.List 16 | mutex sync.RWMutex 17 | len int 18 | cache map[interface{}]*entry 19 | } 20 | 21 | // New returns a new Adaptive Replacement Cache (ARC). 22 | func New(c int) *ARC { 23 | return &ARC{ 24 | p: 0, 25 | c: c, 26 | t1: list.New(), 27 | b1: list.New(), 28 | t2: list.New(), 29 | b2: list.New(), 30 | len: 0, 31 | cache: make(map[interface{}]*entry, c), 32 | } 33 | } 34 | 35 | // Put inserts a new key-value pair into the cache. 36 | // This optimizes future access to this entry (side effect). 37 | func (a *ARC) Put(key, value interface{}) bool { 38 | a.mutex.Lock() 39 | defer a.mutex.Unlock() 40 | 41 | ent, ok := a.cache[key] 42 | if ok != true { 43 | a.len++ 44 | 45 | ent = &entry{ 46 | key: key, 47 | value: value, 48 | ghost: false, 49 | } 50 | 51 | a.req(ent) 52 | a.cache[key] = ent 53 | } else { 54 | if ent.ghost { 55 | a.len++ 56 | } 57 | ent.value = value 58 | ent.ghost = false 59 | a.req(ent) 60 | } 61 | return ok 62 | } 63 | 64 | // Get retrieves a previously via Set inserted entry. 65 | // This optimizes future access to this entry (side effect). 66 | func (a *ARC) Get(key interface{}) (value interface{}, ok bool) { 67 | a.mutex.Lock() 68 | defer a.mutex.Unlock() 69 | 70 | ent, ok := a.cache[key] 71 | if ok { 72 | a.req(ent) 73 | return ent.value, !ent.ghost 74 | } 75 | return nil, false 76 | } 77 | 78 | // Len determines the number of currently cached entries. 79 | // This method is side-effect free in the sense that it does not attempt to optimize random cache access. 80 | func (a *ARC) Len() int { 81 | a.mutex.Lock() 82 | defer a.mutex.Unlock() 83 | 84 | return a.len 85 | } 86 | 87 | func (a *ARC) req(ent *entry) { 88 | if ent.ll == a.t1 || ent.ll == a.t2 { 89 | // Case I 90 | ent.setMRU(a.t2) 91 | } else if ent.ll == a.b1 { 92 | // Case II 93 | // Cache Miss in t1 and t2 94 | 95 | // Adaptation 96 | var d int 97 | if a.b1.Len() >= a.b2.Len() { 98 | d = 1 99 | } else { 100 | d = a.b2.Len() / a.b1.Len() 101 | } 102 | a.p = min(a.p+d, a.c) 103 | 104 | a.replace(ent) 105 | ent.setMRU(a.t2) 106 | } else if ent.ll == a.b2 { 107 | // Case III 108 | // Cache Miss in t1 and t2 109 | 110 | // Adaptation 111 | var d int 112 | if a.b2.Len() >= a.b1.Len() { 113 | d = 1 114 | } else { 115 | d = a.b1.Len() / a.b2.Len() 116 | } 117 | a.p = max(a.p-d, 0) 118 | 119 | a.replace(ent) 120 | ent.setMRU(a.t2) 121 | } else if ent.ll == nil { 122 | // Case IV 123 | 124 | if a.t1.Len()+a.b1.Len() == a.c { 125 | // Case A 126 | if a.t1.Len() < a.c { 127 | a.delLRU(a.b1) 128 | a.replace(ent) 129 | } else { 130 | a.delLRU(a.t1) 131 | } 132 | } else if a.t1.Len()+a.b1.Len() < a.c { 133 | // Case B 134 | if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() >= a.c { 135 | if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() == 2*a.c { 136 | a.delLRU(a.b2) 137 | } 138 | a.replace(ent) 139 | } 140 | } 141 | 142 | ent.setMRU(a.t1) 143 | } 144 | } 145 | 146 | func (a *ARC) delLRU(list *list.List) { 147 | lru := list.Back() 148 | list.Remove(lru) 149 | a.len-- 150 | delete(a.cache, lru.Value.(*entry).key) 151 | } 152 | 153 | func (a *ARC) replace(ent *entry) { 154 | if a.t1.Len() > 0 && ((a.t1.Len() > a.p) || (ent.ll == a.b2 && a.t1.Len() == a.p)) { 155 | lru := a.t1.Back().Value.(*entry) 156 | lru.value = nil 157 | lru.ghost = true 158 | a.len-- 159 | lru.setMRU(a.b1) 160 | } else { 161 | lru := a.t2.Back().Value.(*entry) 162 | lru.value = nil 163 | lru.ghost = true 164 | a.len-- 165 | lru.setMRU(a.b2) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /arc_test.go: -------------------------------------------------------------------------------- 1 | package arc 2 | 3 | import "testing" 4 | 5 | func TestBasic(t *testing.T) { 6 | cache := New(3) 7 | if cache.Len() != 0 { 8 | t.Error("Empty cache should have length 0") 9 | } 10 | 11 | cache.Put("Hello", "World") 12 | if cache.Len() != 1 { 13 | t.Error("Cache should have length 1") 14 | } 15 | 16 | var val interface{} 17 | var ok bool 18 | 19 | if val, ok = cache.Get("Hello"); val != "World" || ok != true { 20 | t.Error("Didn't set \"Hello\" to \"World\"") 21 | } 22 | 23 | cache.Put("Hello", "World1") 24 | if cache.Len() != 1 { 25 | t.Error("Inserting the same entry multiple times shouldn't increase cache size") 26 | } 27 | 28 | if val, ok = cache.Get("Hello"); val != "World1" || ok != true { 29 | t.Error("Didn't update \"Hello\" to \"World1\"") 30 | } 31 | 32 | cache.Put("Hallo", "Welt") 33 | if cache.Len() != 2 { 34 | t.Error("Inserting two different entries should result into lenght=2") 35 | } 36 | 37 | if val, ok = cache.Get("Hallo"); val != "Welt" || ok != true { 38 | t.Error("Didn't set \"Hallo\" to \"Welt\"") 39 | } 40 | } 41 | 42 | func TestBasicReplace(t *testing.T) { 43 | cache := New(3) 44 | 45 | cache.Put("Hello", "Hallo") 46 | cache.Put("World", "Welt") 47 | cache.Get("World") 48 | cache.Put("Cache", "Cache") 49 | cache.Put("Replace", "Ersetzen") 50 | 51 | value, ok := cache.Get("World") 52 | if !ok || value != "Welt" { 53 | t.Error("ARC should have replaced \"Hello\"") 54 | } 55 | 56 | if cache.Len() != 3 { 57 | t.Error("ARC should have a maximum size of 3") 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /entry.go: -------------------------------------------------------------------------------- 1 | package arc 2 | 3 | import ( 4 | "container/list" 5 | ) 6 | 7 | type entry struct { 8 | key interface{} 9 | value interface{} 10 | ll *list.List 11 | el *list.Element 12 | ghost bool 13 | } 14 | 15 | func (e *entry) setLRU(list *list.List) { 16 | e.detach() 17 | e.ll = list 18 | e.el = e.ll.PushBack(e) 19 | } 20 | 21 | func (e *entry) setMRU(list *list.List) { 22 | e.detach() 23 | e.ll = list 24 | e.el = e.ll.PushFront(e) 25 | } 26 | 27 | func (e *entry) detach() { 28 | if e.ll != nil { 29 | e.ll.Remove(e.el) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package arc 2 | 3 | func min(x, y int) int { 4 | if x < y { 5 | return x 6 | } 7 | return y 8 | } 9 | 10 | func max(x, y int) int { 11 | if x > y { 12 | return x 13 | } 14 | return y 15 | } 16 | --------------------------------------------------------------------------------