LRFUExpirer.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. const PINNED_IN_MEMORY = 0x7fffffff
  2. const NOT_IN_LRU = 0x40000000
  3. export const EXPIRED_ENTRY = {
  4. description: 'This cache entry value has been expired from the LRFU cache, and is waiting for garbage collection to be removed.'
  5. }
  6. /* bit pattern:
  7. * < is-in-lru 1 bit > ...< mask/or bits 6 bits > <lru index 2 bits > < position in cache - 22 bits >
  8. */
  9. export class LRFUExpirer {
  10. constructor(options) {
  11. this.lruSize = options && options.lruSize || 0x2000
  12. if (this.lruSize > 0x400000)
  13. throw new Error('The LRU/cache size was larger than the maximum cache size of 16777216 (LRU size of 4194304)')
  14. this.reset()
  15. startTimedCleanup(new WeakRef(this), options && options.cleanupInterval || 60000)
  16. }
  17. delete(entry) {
  18. if (entry.position < NOT_IN_LRU) {
  19. this.lru[(entry.position >> 22) & 3][entry.position & 0x3fffff] = null
  20. }
  21. entry.position |= NOT_IN_LRU
  22. }
  23. used(entry, expirationPriority) {
  24. let originalPosition = entry.position
  25. let orMask
  26. if (expirationPriority < 0) {
  27. // pin this in memory, first remove from LRFU and then mark it as pinned in memory
  28. if (entry.position < NOT_IN_LRU) {
  29. this.lru[(entry.position >> 22) & 3][entry.position & 0x3fffff] = null
  30. }
  31. entry.position = PINNED_IN_MEMORY
  32. return
  33. } else if (entry.position == PINNED_IN_MEMORY && expirationPriority == undefined) {
  34. return
  35. } else if (expirationPriority >= 0) {
  36. let bits = 0
  37. if (expirationPriority > (this.lruSize >> 2))
  38. expirationPriority = this.lruSize >> 2
  39. while (expirationPriority > 0) {
  40. expirationPriority = expirationPriority >> 1
  41. bits++
  42. }
  43. expirationPriority = bits
  44. } else {
  45. if (originalPosition >= 0)
  46. expirationPriority = (originalPosition >> 24) & 0x3f
  47. else
  48. expirationPriority = 0
  49. }
  50. let lruPosition
  51. let lruIndex
  52. if (originalPosition < NOT_IN_LRU) {
  53. lruIndex = (originalPosition >> 22) & 3
  54. if (lruIndex >= 3)
  55. return // can't get any higher than this, don't do anything
  56. let lru = this.lru[lruIndex]
  57. // check to see if it is in the same generation
  58. lruPosition = lru.position
  59. if ((originalPosition > lruPosition ? lruPosition + this.lruSize : lruPosition) - originalPosition < (this.lruSize >> 2))
  60. return // only recently added, don't promote
  61. lru[originalPosition & 0x3fffff] = null // remove it, we are going to move/promote it
  62. lruIndex++
  63. } else
  64. lruIndex = 0
  65. this.insertEntry(entry, lruIndex, expirationPriority)
  66. }
  67. insertEntry(entry, lruIndex, expirationPriority) {
  68. let lruPosition, nextLru = this.lru[lruIndex]
  69. let orMask = 0x3fffff >> (22 - expirationPriority)
  70. do {
  71. // put it in the next lru
  72. lruPosition = nextLru.position | orMask
  73. let previousEntry = nextLru[lruPosition & 0x3fffff]
  74. nextLru[lruPosition & 0x3fffff] = entry
  75. if (entry)
  76. entry.position = lruPosition | (expirationPriority << 24)
  77. nextLru.position = ++lruPosition
  78. if ((lruPosition & 0x3fffff) >= this.lruSize) {
  79. // reset at the beginning of the lru cache
  80. lruPosition &= 0x7fc00000
  81. nextLru.position = lruPosition
  82. nextLru.cycles++
  83. }
  84. entry = previousEntry
  85. if (entry && (nextLru = this.lru[--lruIndex])) {
  86. expirationPriority = ((entry.position || 0) >> 24) & 0x3f
  87. orMask = 0x3fffff >> (22 - expirationPriority)
  88. } else
  89. break
  90. } while (true)
  91. if (entry) {// this one was removed
  92. entry.position |= NOT_IN_LRU
  93. if (entry.cache)
  94. entry.cache.onRemove(entry)
  95. else if (entry.deref) // if we have already registered the entry in the finalization registry, just clear it
  96. entry.value = EXPIRED_ENTRY
  97. }
  98. }
  99. reset() {
  100. /* if (this.lru) {
  101. for (let i = 0; i < 4; i++) {
  102. for (let j = 0, l = this.lru.length; j < l; j++) {
  103. let entry = this.lru[i][j]
  104. if (entry) {// this one was removed
  105. entry.position |= NOT_IN_LRU
  106. if (entry.cache)
  107. entry.cache.onRemove(entry)
  108. else if (entry.deref) // if we have already registered the entry in the finalization registry, just clear it
  109. entry.value = EXPIRED_ENTRY
  110. }
  111. }
  112. }
  113. }*/
  114. this.lru = []
  115. for (let i = 0; i < 4; i++) {
  116. this.lru[i] = new Array(this.lruSize)
  117. this.lru[i].position = i << 22
  118. this.lru[i].cycles = 0
  119. }
  120. }
  121. cleanup() { // clean out a portion of the cache, so we can clean up over time if idle
  122. let toClear = this.lruSize >> 4 // 1/16 of the lru cache at a time
  123. for (let i = 3; i >= 0; i--) {
  124. let lru = this.lru[i]
  125. for (let j = 0, l = toClear; j < l; j++) {
  126. if (lru[lru.position & 0x3fffff]) {
  127. toClear--
  128. this.insertEntry(null, i, 0)
  129. } else {
  130. if ((++lru.position & 0x3fffff) >= this.lruSize) {
  131. // reset at the beginning of the lru cache
  132. lru.position &= 0x7fc00000
  133. lru.cycles++
  134. }
  135. }
  136. }
  137. }
  138. }
  139. }
  140. function startTimedCleanup(reference, cleanupInterval) {
  141. let interval = setInterval(() => {
  142. let expirer = reference.deref()
  143. if (expirer)
  144. expirer.cleanup()
  145. else
  146. clearInterval(interval)
  147. }, cleanupInterval)
  148. if (interval.unref)
  149. interval.unref()
  150. }