1 module memoize; 2 3 import std.traits; 4 5 /** 6 The following code makes cached (memoized) property `f` 7 ``` 8 class { 9 @property string _f() { ... } 10 mixin CachedProperty!"f"; 11 } 12 ``` 13 */ 14 mixin template CachedProperty(string name, string baseName = '_' ~ name) { 15 mixin("private typeof(" ~ baseName ~ ") " ~ name ~ "Cache;"); 16 mixin("private bool " ~ name ~ "IsCached = false;"); 17 mixin("@property typeof(" ~ baseName ~ ") " ~ name ~ "() {\n" ~ 18 "if (" ~ name ~ "IsCached" ~ ") return " ~ name ~ "Cache;\n" ~ 19 name ~ "IsCached = true;\n" ~ 20 "return " ~ name ~ "Cache = " ~ baseName ~ ";\n" ~ 21 '}'); 22 } 23 24 unittest { 25 struct S { 26 @property float _x() { return 1.5; } 27 mixin CachedProperty!"x"; 28 } 29 class C { 30 @property float _x() { return 1.5; } 31 mixin CachedProperty!"x"; 32 } 33 S s; 34 assert(s.x == 1.5); 35 assert(s.x == 1.5); 36 C c = new C(); 37 assert(c.x == 1.5); 38 } 39 40 private template _memoize(alias fun, string attr) 41 { 42 import std.traits : ReturnType; 43 // alias Args = Parameters!fun; // Bugzilla 13580 44 45 ReturnType!fun _memoize(Parameters!fun args) 46 { 47 alias Args = Parameters!fun; 48 import std.typecons : Tuple; 49 50 mixin(attr ~ " static ReturnType!fun[Tuple!Args] memo;"); 51 auto t = Tuple!Args(args); 52 if (auto p = t in memo) 53 return *p; 54 return memo[t] = fun(args); 55 } 56 } 57 58 private template _memoize(alias fun, uint maxSize, string modifier) 59 { 60 import std.traits : ReturnType; 61 // alias Args = Parameters!fun; // Bugzilla 13580 62 ReturnType!fun _memoize(Parameters!fun args) 63 { 64 import std.traits : hasIndirections; 65 import std.typecons : tuple; 66 static struct Value { Parameters!fun args; ReturnType!fun res; } 67 mixin(modifier ~ " static Value[] memo;"); 68 mixin(modifier ~ " static size_t[] initialized;"); 69 70 if (!memo.length) 71 { 72 import core.memory : GC; 73 74 // Ensure no allocation overflows 75 static assert(maxSize < size_t.max / Value.sizeof); 76 static assert(maxSize < size_t.max - (8 * size_t.sizeof - 1)); 77 78 enum attr = GC.BlkAttr.NO_INTERIOR | (hasIndirections!Value ? 0 : GC.BlkAttr.NO_SCAN); 79 mixin("alias VType = " ~ modifier ~ " Value;"); 80 memo = (cast(VType*) GC.malloc(Value.sizeof * maxSize, attr))[0 .. maxSize]; 81 enum nwords = (maxSize + 8 * size_t.sizeof - 1) / (8 * size_t.sizeof); 82 mixin("alias mysize_t = " ~ modifier ~ " size_t;"); 83 initialized = (cast(mysize_t*) GC.calloc(nwords * size_t.sizeof, attr | GC.BlkAttr.NO_SCAN))[0 .. nwords]; 84 } 85 86 import core.bitop : bt, bts; 87 import std.conv : emplace; 88 89 size_t hash; 90 foreach (ref arg; args) 91 hash = hashOf(arg, hash); 92 // cuckoo hashing 93 immutable idx1 = hash % maxSize; 94 if (!bt(cast(size_t*) initialized.ptr, idx1)) 95 { 96 emplace(&memo[idx1], args, fun(args)); 97 bts(cast(size_t*) initialized.ptr, idx1); // only set to initialized after setting args and value (bugzilla 14025) 98 return memo[idx1].res; 99 } 100 else if (memo[idx1].args == args) 101 return memo[idx1].res; 102 // FNV prime 103 immutable idx2 = (hash * 16_777_619) % maxSize; 104 if (!bt(cast(size_t*) initialized.ptr, idx2)) 105 { 106 emplace(&memo[idx2], memo[idx1]); 107 bts(cast(size_t*) initialized.ptr, idx2); // only set to initialized after setting args and value (bugzilla 14025) 108 } 109 else if (memo[idx2].args == args) 110 return memo[idx2].res; 111 else if (idx1 != idx2) 112 memo[idx2] = memo[idx1]; 113 114 memo[idx1] = Value(args, fun(args)); 115 return memo[idx1].res; 116 } 117 } 118 119 /** 120 The same as in Phobos `std.functional`. 121 */ 122 alias memoize(alias fun) = _memoize!(fun, ""); 123 124 /// ditto 125 alias memoize(alias fun, uint maxSize) = _memoize!(fun, maxSize, ""); 126 127 /// must be locked explicitly! 128 alias noLockMemoize(alias fun) = _memoize!(fun, "shared"); 129 130 /// must be locked explicitly! 131 alias noLockMemoize(alias fun, uint maxSize) = _memoize!(fun, maxSize, "shared"); 132 133 /** 134 Synchronized version of `memoize` using global (interthread) cache. 135 */ 136 template synchronizedMemoize(alias fun) { 137 private alias impl = noLockMemoize!fun; 138 ReturnType!fun synchronizedMemoize(Parameters!fun args) { 139 synchronized { 140 return impl(args); 141 } 142 } 143 } 144 145 /// ditto 146 template synchronizedMemoize(alias fun, uint maxSize) { 147 private alias impl = noLockMemoize!(fun, maxSize); 148 ReturnType!fun synchronizedMemoize(Parameters!fun args) { 149 synchronized { 150 return impl(args); 151 } 152 } 153 } 154 155 @safe unittest 156 { 157 ulong fib(ulong n) @safe 158 { 159 return n < 2 ? n : memoize!fib(n - 2) + memoize!fib(n - 1); 160 } 161 assert(fib(10) == 55); 162 ulong fib2(ulong n) @safe 163 { 164 return n < 2 ? n : synchronizedMemoize!fib2(n - 2) + synchronizedMemoize!fib2(n - 1); 165 } 166 assert(fib2(10) == 55); 167 } 168 169 @safe unittest 170 { 171 ulong fact(ulong n) @safe 172 { 173 return n < 2 ? 1 : n * memoize!fact(n - 1); 174 } 175 assert(fact(10) == 3628800); 176 ulong fact2(ulong n) @safe 177 { 178 return n < 2 ? 1 : n * synchronizedMemoize!fact2(n - 1); 179 } 180 assert(fact2(10) == 3628800); 181 } 182 183 @safe unittest 184 { 185 ulong factImpl(ulong n) @safe 186 { 187 return n < 2 ? 1 : n * factImpl(n - 1); 188 } 189 alias fact = memoize!factImpl; 190 assert(fact(10) == 3628800); 191 ulong factImpl2(ulong n) @safe 192 { 193 return n < 2 ? 1 : n * factImpl2(n - 1); 194 } 195 alias fact2 = synchronizedMemoize!factImpl; 196 assert(fact2(10) == 3628800); 197 } 198 199 @system unittest // not @safe due to memoize 200 { 201 ulong fact(ulong n) 202 { 203 // Memoize no more than 8 values 204 return n < 2 ? 1 : n * memoize!(fact, 8)(n - 1); 205 } 206 assert(fact(8) == 40320); 207 // using more entries than maxSize will overwrite existing entries 208 assert(fact(10) == 3628800); 209 ulong fact2(ulong n) 210 { 211 // Memoize no more than 8 values 212 return n < 2 ? 1 : n * synchronizedMemoize!(fact2, 8)(n - 1); 213 } 214 assert(fact2(8) == 40320); 215 // using more entries than maxSize will overwrite existing entries 216 assert(fact2(10) == 3628800); 217 } 218 219 /** 220 Use it to memoize both a struct or class instance for a member function and function arguments like: 221 ``` 222 struct S { 223 int f(int a, int b) { 224 return a + b; 225 } 226 } 227 alias f2 = memoizeMember!(S, "f"); 228 alias f3 = memoizeMember!(S, "f", 10); 229 S s; 230 assert(f2(s, 2, 3) == 5); 231 ``` 232 233 As you see the member function name ("f" in the example) is passed as a string. 234 This is very unnatural, but I found no other way to do it. 235 */ 236 template memoizeMember(S, string name) { 237 alias Member = __traits(getMember, S, name); 238 ReturnType!Member f(S s, Parameters!Member others) { 239 return __traits(getMember, s, name)(others); 240 } 241 alias memoizeMember = memoize!f; 242 } 243 244 /// ditto 245 template memoizeMember(S, string name, uint maxSize) { 246 alias Member = __traits(getMember, S, name); 247 ReturnType!Member f(S s, Parameters!Member others) { 248 return __traits(getMember, s, name)(others); 249 } 250 alias memoizeMember = memoize!(f, maxSize); 251 } 252 253 unittest { 254 struct S2 { 255 int f(int a, int b) { 256 return a + b; 257 } 258 } 259 alias f2 = memoizeMember!(S2, "f"); 260 alias f3 = memoizeMember!(S2, "f", 10); 261 S2 s; 262 assert(f2(s, 2, 3) == 5); 263 assert(f2(s, 2, 3) == 5); 264 assert(f3(s, 2, 3) == 5); 265 }