Last active
December 8, 2022 19:34
-
-
Save isaacs/4d700a8b80a3f898bc25c1f34aa84dda to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/index.js b/index.js | |
index fa53c12..620cf57 100644 | |
--- a/index.js | |
+++ b/index.js | |
@@ -849,6 +849,114 @@ class LRUCache { | |
} | |
} | |
+ getSet( | |
+ k, | |
+ v, | |
+ { | |
+ // get options | |
+ allowStale = this.allowStale, | |
+ updateAgeOnGet = this.updateAgeOnGet, | |
+ noDeleteOnStaleGet = this.noDeleteOnStaleGet, | |
+ // set options | |
+ ttl = this.ttl, | |
+ start, | |
+ noDisposeOnSet = this.noDisposeOnSet, | |
+ size = 0, | |
+ sizeCalculation = this.sizeCalculation, | |
+ noUpdateTTL = this.noUpdateTTL, | |
+ } = {} | |
+ ) { | |
+ let index = this.size === 0 ? undefined : this.keyMap.get(k) | |
+ if (index !== undefined) { | |
+ const value = this.valList[index] | |
+ const fetching = this.isBackgroundFetch(value) | |
+ if (this.isStale(index)) { | |
+ // delete only if not an in-flight background fetch | |
+ if (!fetching) { | |
+ if (allowStale) { | |
+ // only need to delete the stale entry if we're returning it | |
+ // otherwise we'll overwrite it soon anyway. | |
+ if (!noDeleteOnStaleGet) { | |
+ this.delete(k) | |
+ } | |
+ return value | |
+ } | |
+ } else { | |
+ if ( | |
+ allowStale && | |
+ value.__staleWhileFetching !== undefined | |
+ ) { | |
+ return value.__staleWhileRefetching | |
+ } | |
+ } | |
+ } else { | |
+ // if we're currently fetching it, we don't actually have it yet | |
+ // it's not stale, which means this isn't a staleWhileRefetching, | |
+ // so we just return undefined | |
+ if (!fetching) { | |
+ this.moveToTail(index) | |
+ if (updateAgeOnGet) { | |
+ this.updateItemAge(index) | |
+ } | |
+ return value | |
+ } | |
+ } | |
+ } | |
+ | |
+ // ok, either we don't have it, or it's stale and we don't allowStale | |
+ // set and return the new value. this is the same as set(), but | |
+ // takes advantage of the fact that we may already know the index. | |
+ size = this.requireSize(k, v, size, sizeCalculation) | |
+ if (this.maxEntrySize && size > this.maxEntrySize) { | |
+ this.delete(k) | |
+ return v | |
+ } | |
+ if (index === undefined) { | |
+ // addition | |
+ index = this.newIndex() | |
+ this.keyList[index] = k | |
+ this.valList[index] = v | |
+ this.keyMap.set(k, index) | |
+ this.next[this.tail] = index | |
+ this.prev[index] = this.tail | |
+ this.tail = index | |
+ this.size++ | |
+ this.addItemSize(index, size) | |
+ noUpdateTTL = false | |
+ } else { | |
+ // update | |
+ const oldVal = this.valList[index] | |
+ if (v !== oldVal) { | |
+ if (this.isBackgroundFetch(oldVal)) { | |
+ oldVal.__abortController.abort() | |
+ } else { | |
+ if (!noDisposeOnSet) { | |
+ this.dispose(oldVal, k, 'set') | |
+ if (this.disposeAfter) { | |
+ this.disposed.push([oldVal, k, 'set']) | |
+ } | |
+ } | |
+ } | |
+ this.removeItemSize(index) | |
+ this.valList[index] = v | |
+ this.addItemSize(index, size) | |
+ } | |
+ this.moveToTail(index) | |
+ } | |
+ if (ttl !== 0 && this.ttl === 0 && !this.ttls) { | |
+ this.initializeTTLTracking() | |
+ } | |
+ if (!noUpdateTTL) { | |
+ this.setItemTTL(index, ttl, start) | |
+ } | |
+ if (this.disposeAfter) { | |
+ while (this.disposed.length) { | |
+ this.disposeAfter(...this.disposed.shift()) | |
+ } | |
+ } | |
+ return v | |
+ } | |
+ | |
get( | |
k, | |
{ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const LRUCache = require('../') | |
const bench = require('bench') | |
const N = 1000 | |
const keys = new Array(N) | |
for (let i = 0; i < N; i++) { | |
keys[i] = Math.random() | |
} | |
const autoGetSetCache = new LRUCache({ max: N * 10 }) | |
// fill up first | |
for (let i = 0; i < N; i++) { | |
autoGetSetCache.set(keys[i], i) | |
} | |
const autoGetSet = () => { | |
// do 2000 sets, alternating between known and unknown values | |
for (let i = 0; i < N; i++) { | |
autoGetSetCache.getSet(keys[i], i + 1) | |
autoGetSetCache.getSet(Math.random(), i) | |
} | |
} | |
const manualHasGetSetCache = new LRUCache({ max: N * 10 }) | |
for (let i = 0; i < N; i++) { | |
manualHasGetSetCache.set(keys[i], i) | |
} | |
const manualHasGetSet = () => { | |
for (let i = 0; i < N; i++) { | |
const k1 = keys[i] | |
if (manualHasGetSetCache.has(k1)) { | |
manualHasGetSetCache.get(k1) | |
} else { | |
manualHasGetSetCache.set(k1, i + 1) | |
} | |
const k2 = Math.random() | |
if (manualHasGetSetCache.has(k2)) { | |
manualHasGetSetCache.get(k2) | |
} else { | |
manualHasGetSetCache.set(k2, Math.random()) | |
} | |
} | |
} | |
const manualGetSetCache = new LRUCache({ max: N * 10 }) | |
for (let i = 0; i < N; i++) { | |
manualGetSetCache.set(keys[i], i) | |
} | |
const manualGetSet = () => { | |
for (let i = 0; i < N; i++) { | |
const k1 = keys[i] | |
if (manualGetSetCache.get(k1) === undefined) { | |
manualGetSetCache.set(k1, i + 1) | |
} | |
const k2 = Math.random() | |
if (manualGetSetCache.get(k2) === undefined) { | |
manualGetSetCache.set(k2, Math.random()) | |
} | |
} | |
} | |
// do a card deck style cut-and-merge shuffle, | |
// but not a perfect shuffle, muck it up it a bit. | |
const shuffle = () => { | |
const high = keys.slice(0, N/2) | |
const low = keys.slice(N/2) | |
keys.length = 0 | |
for (let i = 0; i < N/2; i++) { | |
const rif = Math.random() < 0.5 | |
keys.push(rif ? high[i] : low[i]) | |
keys.push(rif ? low[i] : high[i]) | |
} | |
} | |
// shuffle 7 times | |
for (let i = 0; i < 7; i++) { | |
shuffle() | |
} | |
exports.compare = { | |
autoGetSet, | |
manualHasGetSet, | |
manualGetSet, | |
} | |
bench.runMain() | |
/* | |
benchmarking /Users/isaacs/dev/isaacs/lru-cache/x/index.js | |
Please be patient. | |
{ | |
node: '19.1.0', | |
v8: '10.7.193.20-node.19', | |
uv: '1.44.2', | |
zlib: '1.2.11', | |
brotli: '1.0.9', | |
ares: '1.18.1', | |
modules: '111', | |
nghttp2: '1.47.0', | |
napi: '8', | |
llhttp: '8.1.0', | |
openssl: '3.0.7+quic', | |
cldr: '42.0', | |
icu: '72.1', | |
tz: '2022e', | |
unicode: '15.0', | |
ngtcp2: '0.8.1', | |
nghttp3: '0.7.0' | |
} | |
Scores: (bigger is better) | |
autoGetSet | |
Raw: | |
> 5.514705882352941 | |
> 5.45950864422202 | |
> 5.449591280653951 | |
> 5.489478499542543 | |
Average (mean) 5.478321076692864 | |
manualGetSet | |
Raw: | |
> 5.016722408026756 | |
> 5.008347245409015 | |
> 4.930966469428008 | |
> 5 | |
Average (mean) 4.989009030715945 | |
manualHasGetSet | |
Raw: | |
> 4.725897920604915 | |
> 4.734848484848484 | |
> 4.752851711026616 | |
> 4.7664442326024785 | |
Average (mean) 4.745010587270623 | |
Winner: autoGetSet | |
Compared with next highest (manualGetSet), it's: | |
8.93% faster | |
1.1 times as fast | |
0.04 order(s) of magnitude faster | |
A LITTLE FASTER | |
Compared with the slowest (manualHasGetSet), it's: | |
13.39% faster | |
1.15 times as fast | |
0.06 order(s) of magnitude faster | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"dependencies": { | |
"bench": "^0.3.6", | |
"lru-cache": "file:.." | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
These numbers are in kHz (ops / ms), and each "operation" is actually 2000 get/set operations (1000 with existing keys, 1000 with new keys).
So, best case, this is speeding it up from about 9,500,000 operations per second to about 10,000,000.