1 /* 2 This file is part of BioD. 3 Copyright (C) 2012 Artem Tarasov <lomereiter@gmail.com> 4 5 Permission is hereby granted, free of charge, to any person obtaining a 6 copy of this software and associated documentation files (the "Software"), 7 to deal in the Software without restriction, including without limitation 8 the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 and/or sell copies of the Software, and to permit persons to whom the 10 Software is furnished to do so, subject to the following conditions: 11 12 The above copyright notice and this permission notice shall be included in 13 all 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 20 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 DEALINGS IN THE SOFTWARE. 22 23 */ 24 module bio.core.utils.memoize; 25 26 import std.traits; 27 import std.typecons; 28 29 debug { 30 import std.stdio; 31 } 32 33 /// 34 interface Cache(K, V) { 35 /// 36 V* lookup(K key); 37 38 /// 39 void put(K key, V value); 40 } 41 42 /// Elements are not removed from the cache. 43 class BasicCache(uint maxItems, K, V) : Cache!(K, V) { 44 private V[K] cache; 45 46 V* lookup(K key) { 47 return key in cache; 48 } 49 50 void put(K key, V value) { 51 cache[key] = value; 52 } 53 } 54 55 /// First-in first-out element removal. 56 class FifoCache(uint maxItems, K, V) : Cache!(K, V) { 57 private V[K] cache; 58 private int items = 0; 59 bool full = false; 60 private K[maxItems] keys; // cyclic queue 61 private uint removals; 62 63 V* lookup(K key) { 64 return key in cache; 65 } 66 67 void put(K key, V value) { 68 cache[key] = value; 69 70 if (full) { 71 cache.remove(keys[items]); 72 removals += 1; 73 74 if (removals % maxItems == 0) { 75 cache.rehash; 76 } 77 } 78 79 keys[items] = key; 80 items += 1; 81 82 if (items == maxItems) { 83 full = true; 84 items = 0; 85 } 86 } 87 } 88 89 /// Least used strategy 90 class LuCache(uint maxItems, K, V) : Cache!(K, V) { 91 private { 92 V[K] cache; 93 int[K] counter; 94 uint removals; 95 } 96 97 V* lookup(K key) { 98 auto result = key in cache; 99 if (result !is null) { 100 counter[key] += 1; 101 } 102 return result; 103 } 104 105 void put(K key, V value) { 106 if (counter.length >= maxItems) { 107 // delete one element before inserting next 108 int min = int.max; 109 K min_key; 110 foreach (k, i; counter) { 111 if (i < min) { 112 min = i; 113 min_key = k; 114 } 115 } 116 117 cache.remove(min_key); 118 counter.remove(min_key); 119 removals += 1; 120 121 if (removals % maxItems == 0) { 122 cache.rehash; 123 counter.rehash; 124 } 125 } 126 cache[key] = value; 127 counter[key] = 1; 128 } 129 } 130 131 version(unittest) { 132 /// keeps number of function evaluations 133 static shared evaluations = 0; 134 static shared int hits = 0; 135 static shared int misses = 0; 136 137 import std.stdio; 138 import core.atomic; 139 void printStats() { 140 writeln("hits: ", hits, " misses: ", misses); 141 } 142 } 143 144 auto memoize(alias func, uint maxItems=1024, 145 alias CacheImpl=BasicCache, Args...)(Args args) 146 if(isCallable!func && is(Args == ParameterTypeTuple!func) 147 && Args.length > 0) 148 { 149 alias ReturnType!func R; 150 151 static if (Args.length == 1) { 152 static shared(CacheImpl!(maxItems, Args, R)) cache; 153 } else { 154 static shared(CacheImpl!(maxItems, Tuple!Args, R)) cache; 155 } 156 static shared bool init = false; 157 158 if (!init) { 159 synchronized { 160 if (cache is null) { 161 static if (Args.length == 1) { 162 cache = new shared(CacheImpl!(maxItems, Args, R))(); 163 } else { 164 cache = new shared(CacheImpl!(maxItems, Tuple!Args, R))(); 165 } 166 } 167 init = true; 168 } 169 } 170 171 static if (Args.length == 1) { 172 auto key = args; 173 } else { 174 auto key = tuple(args); 175 } 176 177 R* ret = (cast()cache).lookup(key); 178 if (ret !is null) { 179 version(unittest) { 180 atomicOp!"+="(hits, 1); 181 } 182 return *ret; 183 } else { 184 version(unittest) { 185 atomicOp!"+="(misses, 1); 186 } 187 synchronized(cache) { 188 189 ret = (cast()cache).lookup(key); 190 if (ret !is null) { 191 return *ret; 192 } 193 194 version(unittest) { 195 evaluations.atomicOp!"+="(1); 196 } 197 198 auto result = func(args); 199 (cast()cache).put(key, result); 200 return result; 201 } 202 } 203 } 204 205 unittest { 206 207 import core.thread; 208 209 evaluations = 0; 210 /// very simple function for testing 211 int func(int x, int y) { 212 return x * y; 213 } 214 215 /// 4 different argument values in total 216 void thread_func() { 217 memoize!func(5, 10); 218 memoize!func(3, 7); 219 memoize!func(6, 4); 220 memoize!func(5, 10); 221 memoize!func(7, 9); 222 } 223 224 Thread[] threads = new Thread[5]; 225 foreach (i; 0 .. 5) threads[i] = new Thread(&thread_func); 226 foreach (i; 0 .. 5) threads[i].start(); 227 foreach (i; 0 .. 5) threads[i].join(); 228 229 /// number of evaluations must be the same as number of 230 /// different argument values (4 in this case) 231 assert(evaluations == 4); 232 233 /// test FIFO cache 234 alias memoize!(func, 2, FifoCache, int, int) fifomemoize; 235 evaluations = 0; 236 237 fifomemoize(1, 5); // 5 238 fifomemoize(2, 3); // 5 6 239 fifomemoize(2, 4); // 6 8 240 fifomemoize(1, 5); // 8 5 241 assert(evaluations == 4); 242 fifomemoize(2, 4); // 8 5 243 assert(evaluations == 4); 244 fifomemoize(1, 7); // 5 7 245 fifomemoize(1, 5); // 5 7 246 assert(evaluations == 5); 247 248 int foo(int x) { 249 return x; 250 } 251 /// Test LU cache 252 alias memoize!(foo, 3, LuCache, int) lumemoize; 253 evaluations = 0; 254 255 lumemoize(1); 256 lumemoize(1); 257 lumemoize(1); // 1 258 lumemoize(2); 259 lumemoize(2); // 1, 2 260 lumemoize(3); 261 lumemoize(3); 262 lumemoize(3); // 1, 2, 3 263 lumemoize(4); // 2 -> 4 264 lumemoize(2); // 4 -> 2 265 assert(evaluations == 5); 266 lumemoize(3); // 1, 2, 3 267 lumemoize(5); // 2 -> 5 268 assert(evaluations == 6); 269 lumemoize(4); // 5 -> 4 270 lumemoize(9); // 4 -> 9 271 assert(evaluations == 8); 272 }