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 // Ugh, repeated code 41 private template _memoize(alias fun, string attr) 42 { 43 // alias Args = Parameters!fun; // Bugzilla 13580 44 45 ReturnType!fun _memoize(const(Parameters!fun) args) 46 { 47 alias Args = Parameters!fun; 48 import std.typecons : Tuple; 49 50 mixin(attr ~ " static Unqual!(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 _referenceMemoize(alias fun, string attr) 59 { 60 // alias Args = Parameters!fun; // Bugzilla 13580 61 62 ref Unqual!(ReturnType!fun) _referenceMemoize(const(Parameters!fun) args) 63 { 64 alias Args = Parameters!fun; 65 import std.typecons : Tuple; 66 67 static ReturnType!fun*[Tuple!Args] memo; 68 auto t = Tuple!Args(args); 69 if (auto p = t in memo) return **p; 70 memo[t] = &fun(args); 71 return *memo[t]; 72 } 73 } 74 75 // TODO: It does not compile when modifier != "" 76 // (also compiling with different modifiers would use more program memory) 77 private template _memoize(alias fun, uint maxSize, string modifier) 78 { 79 // alias Args = Parameters!fun; // Bugzilla 13580 80 // Ugh, repeated code 81 ReturnType!fun _memoize(const(Parameters!fun) args) 82 { 83 import std.traits : hasIndirections; 84 import std.typecons : tuple; 85 static struct Value { const(Parameters!fun) args; ReturnType!fun res; } 86 mixin(modifier ~ " static Unqual!(Value)[] memo;"); 87 mixin(modifier ~ " static size_t[] initialized;"); 88 89 if (!memo.length) 90 { 91 import core.memory : GC; 92 93 // Ensure no allocation overflows 94 static assert(maxSize < size_t.max / Value.sizeof); 95 static assert(maxSize < size_t.max - (8 * size_t.sizeof - 1)); 96 97 enum attr = GC.BlkAttr.NO_INTERIOR | (hasIndirections!Value ? 0 : GC.BlkAttr.NO_SCAN); 98 mixin("alias VType = " ~ modifier ~ " Value;"); 99 memo = (cast(VType*) GC.malloc(Value.sizeof * maxSize, attr))[0 .. maxSize]; 100 enum nwords = (maxSize + 8 * size_t.sizeof - 1) / (8 * size_t.sizeof); 101 mixin("alias mysize_t = " ~ modifier ~ " size_t;"); 102 initialized = (cast(mysize_t*) GC.calloc(nwords * size_t.sizeof, attr | GC.BlkAttr.NO_SCAN))[0 .. nwords]; 103 } 104 105 import core.bitop : bt, bts; 106 import std.conv : emplace; 107 108 size_t hash; 109 foreach (ref arg; args) 110 hash = hashOf(arg, hash); 111 // cuckoo hashing 112 immutable idx1 = hash % maxSize; 113 if (!bt(cast(size_t*) initialized.ptr, idx1)) 114 { 115 emplace(cast(Unqual!(Value*)) &memo[idx1], args, fun(args)); 116 bts(cast(size_t*) initialized.ptr, idx1); // only set to initialized after setting args and value (bugzilla 14025) 117 return memo[idx1].res; 118 } 119 else if (memo[idx1].args == args) 120 return memo[idx1].res; 121 // FNV prime 122 immutable idx2 = (hash * 16_777_619) % maxSize; 123 if (!bt(cast(size_t*) initialized.ptr, idx2)) 124 { 125 emplace(&memo[idx2], memo[idx1]); 126 bts(cast(size_t*) initialized.ptr, idx2); // only set to initialized after setting args and value (bugzilla 14025) 127 } 128 else if (memo[idx2].args == args) 129 return memo[idx2].res; 130 else if (idx1 != idx2) 131 memo[idx2] = memo[idx1]; 132 133 memo[idx1] = Value(args, fun(args)); 134 return memo[idx1].res; 135 } 136 } 137 138 private template _referenceMemoize(alias fun, uint maxSize, string modifier) 139 { 140 ref Unqual!(ReturnType!fun) _referenceMemoize(const(Parameters!fun) args) 141 { 142 import std.traits : hasIndirections; 143 import std.typecons : tuple; 144 static struct Value { const(Parameters!fun) args; ReturnType!fun *res; } 145 mixin(modifier ~ " static Unqual!(Value)*[] memo;"); 146 mixin(modifier ~ " static size_t[] initialized;"); 147 148 if (!memo.length) 149 { 150 import core.memory : GC; 151 152 // Ensure no allocation overflows 153 static assert(maxSize < size_t.max / Value.sizeof); 154 static assert(maxSize < size_t.max - (8 * size_t.sizeof - 1)); 155 156 enum attr = GC.BlkAttr.NO_INTERIOR | (hasIndirections!Value ? 0 : GC.BlkAttr.NO_SCAN); 157 mixin("alias VType = " ~ modifier ~ " Value;"); 158 memo = (cast(VType*) GC.malloc(Value.sizeof * maxSize, attr))[0 .. maxSize]; 159 enum nwords = (maxSize + 8 * size_t.sizeof - 1) / (8 * size_t.sizeof); 160 mixin("alias mysize_t = " ~ modifier ~ " size_t;"); 161 initialized = (cast(mysize_t*) GC.calloc(nwords * size_t.sizeof, attr | GC.BlkAttr.NO_SCAN))[0 .. nwords]; 162 } 163 164 import core.bitop : bt, bts; 165 import std.conv : emplace; 166 167 size_t hash; 168 foreach (ref arg; args) 169 hash = hashOf(arg, hash); 170 // cuckoo hashing 171 immutable idx1 = hash % maxSize; 172 if (!bt(cast(size_t*) initialized.ptr, idx1)) 173 { 174 emplace(&memo[idx1], args, &fun(args)); 175 bts(cast(size_t*) initialized.ptr, idx1); // only set to initialized after setting args and value (bugzilla 14025) 176 return *memo[idx1].res; 177 } 178 else if (memo[idx1].args == args) 179 return *memo[idx1].res; 180 // FNV prime 181 immutable idx2 = (hash * 16_777_619) % maxSize; 182 if (!bt(cast(size_t*) initialized.ptr, idx2)) 183 { 184 emplace(&memo[idx2], memo[idx1]); 185 bts(cast(size_t*) initialized.ptr, idx2); // only set to initialized after setting args and value (bugzilla 14025) 186 } 187 else if (memo[idx2].args == args) 188 return *memo[idx2].res; 189 else if (idx1 != idx2) 190 memo[idx2] = memo[idx1]; 191 192 memo[idx1] = Value(args, &fun(args)); 193 return *memo[idx1].res; 194 } 195 } 196 197 // See https://issues.dlang.org/show_bug.cgi?id=4533 why aliases are not used. 198 199 /** 200 The same as in Phobos `std.functional`. 201 */ 202 //alias memoize(alias fun) = _memoize!(fun, ""); 203 template memoize(alias fun) 204 { 205 ReturnType!fun memoize(const(Parameters!fun) args) 206 { 207 return _memoize!(fun, "")(args); 208 } 209 } 210 211 template referenceMemoize(alias fun) 212 { 213 ref Unqual!(ReturnType!fun) referenceMemoize(const(Parameters!fun) args) 214 { 215 return _referenceMemoize!(fun, "")(args); 216 } 217 } 218 219 /// ditto 220 //alias memoize(alias fun, uint maxSize) = _memoize!(fun, maxSize, ""); 221 template memoize(alias fun, uint maxSize) 222 { 223 ReturnType!fun memoize(const(Parameters!fun) args) 224 { 225 return _memoize!(fun, maxSize, "")(args); 226 } 227 } 228 229 template referenceMemoize(alias fun, uint maxSize) 230 { 231 ref Unqual!(ReturnType!fun) referenceMemoize(const(Parameters!fun) args) 232 { 233 return _referenceMemoize!(fun, maxSize, "")(args); 234 } 235 } 236 237 /// must be locked explicitly! 238 //alias noLockMemoize(alias fun) = _memoize!(fun, "shared"); 239 template noLockMemoize(alias fun) 240 { 241 ReturnType!fun noLockMemoize(const(Parameters!fun) args) 242 { 243 return _memoize!(fun, "shared")(args); 244 } 245 } 246 247 template referenceNoLockMemoize(alias fun) 248 { 249 ref Unqual!(ReturnType!fun) referenceNoLockMemoize(const(Parameters!fun) args) 250 { 251 return _referenceMemoize!(fun, "shared")(args); 252 } 253 } 254 255 /// must be locked explicitly! 256 //alias noLockMemoize(alias fun, uint maxSize) = _memoize!(fun, maxSize, "shared"); 257 template noLockMemoize(alias fun, uint maxSize) 258 { 259 ReturnType!fun noLockMemoize(const(Parameters!fun) args) 260 { 261 //return _memoize!(fun, maxSize, "shared")(args); // does not compile 262 return _memoize!(fun, maxSize, "")(args); 263 } 264 } 265 266 template referenceNoLockMemoize(alias fun, uint maxSize) 267 { 268 ref Unqual!(ReturnType!fun) referenceNoLockMemoize(const(Parameters!fun) args) 269 { 270 return _referenceMemoize!(fun, maxSize, "shared")(args); 271 } 272 } 273 274 /** 275 Synchronized version of `memoize` using global (interthread) cache. 276 */ 277 template synchronizedMemoize(alias fun) { 278 private alias impl = memoize!fun; 279 ReturnType!fun synchronizedMemoize(const(Parameters!fun) args) { 280 synchronized { 281 return impl(args); 282 } 283 } 284 } 285 286 template referenceSynchronizedMemoize(alias fun) { 287 private alias impl = referenceMemoize!fun; 288 ref Unqual!(ReturnType!fun) referenceSynchronizedMemoize(const(Parameters!fun) args) { 289 synchronized { 290 return impl(args); 291 } 292 } 293 } 294 295 /// ditto 296 template synchronizedMemoize(alias fun, uint maxSize) { 297 private alias impl = memoize!(fun, maxSize); 298 ReturnType!fun synchronizedMemoize(const(Parameters!fun) args) { 299 synchronized { 300 return impl(args); 301 } 302 } 303 } 304 305 template referenceSynchronizedMemoize(alias fun, uint maxSize) { 306 private alias impl = referenceMemoize!(fun, maxSize); 307 ref Unqual!(ReturnType!fun) referenceSynchronizedMemoize(const(Parameters!fun) args) { 308 synchronized { 309 return impl(args); 310 } 311 } 312 } 313 314 @safe unittest 315 { 316 ulong fib(ulong n) @safe 317 { 318 return n < 2 ? n : memoize!fib(n - 2) + memoize!fib(n - 1); 319 } 320 assert(fib(10) == 55); 321 ulong fib2(ulong n) @safe 322 { 323 return n < 2 ? n : synchronizedMemoize!fib2(n - 2) + synchronizedMemoize!fib2(n - 1); 324 } 325 assert(fib2(10) == 55); 326 ulong fib3(ulong n) @safe 327 { 328 return n < 2 ? n : noLockMemoize!fib3(n - 2) + noLockMemoize!fib3(n - 1); 329 } 330 assert(fib3(10) == 55); 331 } 332 333 @safe unittest 334 { 335 ulong fact(ulong n) @safe 336 { 337 return n < 2 ? 1 : n * memoize!fact(n - 1); 338 } 339 assert(fact(10) == 3628800); 340 ulong fact2(ulong n) @safe 341 { 342 return n < 2 ? 1 : n * synchronizedMemoize!fact2(n - 1); 343 } 344 assert(fact2(10) == 3628800); 345 ulong fact3(ulong n) @safe 346 { 347 return n < 2 ? 1 : n * noLockMemoize!fact3(n - 1); 348 } 349 assert(fact3(10) == 3628800); 350 } 351 352 @safe unittest 353 { 354 ulong factImpl(ulong n) @safe 355 { 356 return n < 2 ? 1 : n * factImpl(n - 1); 357 } 358 alias fact = memoize!factImpl; 359 assert(fact(10) == 3628800); 360 ulong factImpl2(ulong n) @safe 361 { 362 return n < 2 ? 1 : n * factImpl2(n - 1); 363 } 364 alias fact2 = synchronizedMemoize!factImpl; 365 assert(fact2(10) == 3628800); 366 ulong factImpl3(ulong n) @safe 367 { 368 return n < 2 ? 1 : n * factImpl3(n - 1); 369 } 370 alias fact3 = noLockMemoize!factImpl; 371 assert(fact3(10) == 3628800); 372 } 373 374 @system unittest // not @safe due to memoize 375 { 376 ulong fact(ulong n) 377 { 378 // Memoize no more than 8 values 379 return n < 2 ? 1 : n * memoize!(fact, 8)(n - 1); 380 } 381 assert(fact(8) == 40320); 382 // using more entries than maxSize will overwrite existing entries 383 assert(fact(10) == 3628800); 384 ulong fact2(ulong n) 385 { 386 // Memoize no more than 8 values 387 return n < 2 ? 1 : n * synchronizedMemoize!(fact2, 8)(n - 1); 388 } 389 assert(fact2(8) == 40320); 390 // using more entries than maxSize will overwrite existing entries 391 assert(fact2(10) == 3628800); 392 ulong fact3(ulong n) 393 { 394 // Memoize no more than 8 values 395 return n < 2 ? 1 : n * noLockMemoize!(fact3, 8)(n - 1); 396 } 397 assert(fact3(8) == 40320); 398 // using more entries than maxSize will overwrite existing entries 399 assert(fact3(10) == 3628800); 400 } 401 402 @system unittest 403 { 404 static ulong x; 405 ref ulong f() 406 { 407 return x; 408 } 409 assert(&referenceMemoize!f() == &x); 410 assert(&referenceSynchronizedMemoize!f() == &x); 411 assert(&referenceNoLockMemoize!f() == &x); 412 } 413 414 /** 415 Use it to memoize both a struct or class instance for a member function and function arguments like: 416 ``` 417 struct S { 418 int f(int a, int b) { 419 return a + b; 420 } 421 } 422 alias f2 = memoizeMember!(S, "f"); 423 alias f3 = memoizeMember!(S, "f", 10); 424 S s; 425 assert(f2(s, 2, 3) == 5); 426 ``` 427 428 As you see the member function name ("f" in the example) is passed as a string. 429 This is very unnatural, but I found no other way to do it. 430 */ 431 template memoizeMember(S, string name) { 432 alias Member = __traits(getMember, S, name); 433 ReturnType!Member f(S s, Parameters!Member others) { 434 return __traits(getMember, s, name)(others); 435 } 436 alias memoizeMember = memoize!f; 437 } 438 439 template referenceMemoizeMember(S, string name) { 440 alias Member = __traits(getMember, S, name); 441 ref ReturnType!Member f(S s, Parameters!Member others) { 442 return __traits(getMember, s, name)(others); 443 } 444 alias referenceMemoizeMember = referenceMemoize!f; 445 } 446 447 /// ditto 448 template memoizeMember(S, string name, uint maxSize) { 449 alias Member = __traits(getMember, S, name); 450 ReturnType!Member f(S s, Parameters!Member others) { 451 return __traits(getMember, s, name)(others); 452 } 453 alias memoizeMember = memoize!(f, maxSize); 454 } 455 456 template referenceMemoizeMember(S, string name, uint maxSize) { 457 alias Member = __traits(getMember, S, name); 458 ref ReturnType!Member f(S s, Parameters!Member others) { 459 return __traits(getMember, s, name)(others); 460 } 461 alias referenceMemoizeMember = referenceMemoize!(f, maxSize); 462 } 463 464 /// ditto 465 template noLockMemoizeMember(S, string name) { 466 alias Member = __traits(getMember, S, name); 467 ReturnType!Member f(S s, Parameters!Member others) { 468 return __traits(getMember, s, name)(others); 469 } 470 alias noLockMemoizeMember = noLockMemoize!f; 471 } 472 473 template referenceNoLockMemoizeMember(S, string name) { 474 alias Member = __traits(getMember, S, name); 475 ref ReturnType!Member f(S s, Parameters!Member others) { 476 return __traits(getMember, s, name)(others); 477 } 478 alias referenceNoLockMemoizeMember = referenceNoLockMemoize!f; 479 } 480 481 /// ditto 482 template noLockMemoizeMember(S, string name, uint maxSize) { 483 alias Member = __traits(getMember, S, name); 484 ReturnType!Member f(S s, Parameters!Member others) { 485 return __traits(getMember, s, name)(others); 486 } 487 alias noLockMemoizeMember = noLockMemoize!(f, maxSize); 488 } 489 490 template referenceNoLockMemoizeMember(S, string name, uint maxSize) { 491 alias Member = __traits(getMember, S, name); 492 ref ReturnType!Member f(S s, Parameters!Member others) { 493 return __traits(getMember, s, name)(others); 494 } 495 alias referenceNoLockMemoizeMember = referenceNoLockMemoize!(f, maxSize); 496 } 497 498 /// ditto 499 template synchronizedMemoizeMember(S, string name) { 500 alias Member = __traits(getMember, S, name); 501 ReturnType!Member f(S s, Parameters!Member others) { 502 return __traits(getMember, s, name)(others); 503 } 504 alias synchronizedMemoizeMember = synchronizedMemoize!f; 505 } 506 507 template referenceSynchronizedMemoizeMember(S, string name) { 508 alias Member = __traits(getMember, S, name); 509 ref ReturnType!Member f(S s, Parameters!Member others) { 510 return __traits(getMember, s, name)(others); 511 } 512 alias referenceSynchronizedMemoizeMember = referenceSynchronizedMemoize!f; 513 } 514 515 /// ditto 516 template synchronizedMemoizeMember(S, string name, uint maxSize) { 517 alias Member = __traits(getMember, S, name); 518 ReturnType!Member f(S s, Parameters!Member others) { 519 return __traits(getMember, s, name)(others); 520 } 521 alias synchronizedMemoizeMember = synchronizedMemoize!(f, maxSize); 522 } 523 524 template referenceSynchronizedMemoizeMember(S, string name, uint maxSize) { 525 alias Member = __traits(getMember, S, name); 526 ref ReturnType!Member f(S s, Parameters!Member others) { 527 return __traits(getMember, s, name)(others); 528 } 529 alias referenceSynchronizedMemoizeMember = referenceSynchronizedMemoize!(f, maxSize); 530 } 531 532 unittest { 533 struct S2 { 534 int f(int a, int b) { 535 return a + b; 536 } 537 } 538 alias f2 = memoizeMember!(S2, "f"); 539 alias f3 = memoizeMember!(S2, "f", 10); 540 alias f4 = synchronizedMemoizeMember!(S2, "f"); 541 alias f5 = synchronizedMemoizeMember!(S2, "f", 10); 542 alias f6 = noLockMemoizeMember!(S2, "f"); 543 alias f7 = noLockMemoizeMember!(S2, "f", 10); 544 S2 s; 545 assert(f2(s, 2, 3) == 5); 546 assert(f2(s, 2, 3) == 5); 547 assert(f3(s, 2, 3) == 5); 548 assert(f4(s, 2, 3) == 5); 549 assert(f5(s, 2, 3) == 5); 550 assert(f6(s, 2, 3) == 5); 551 assert(f7(s, 2, 3) == 5); 552 } 553 554 unittest { 555 int x; 556 struct S2 { 557 ref int f() { 558 return x; 559 } 560 } 561 S2 s; 562 assert(&referenceMemoizeMember!(S2, "f")(s) == &x); 563 assert(&referenceSynchronizedMemoizeMember!(S2, "f")(s) == &x); 564 assert(&referenceNoLockMemoizeMember!(S2, "f")(s) == &x); 565 }