diff --git a/README.md b/README.md index 85b2e6d..4c6be9d 100644 --- a/README.md +++ b/README.md @@ -7,86 +7,137 @@ The devs [caiogondim](https://github.com/caiogondim) and [planttheidea](https:// The minified/gzipped size is 887 bytes for `nano-memoize` vs 959 bytes for `micro-memoize`. And, `nano-memoize` has slightly more functionality. -The speed tests are below. `nano-memoize` is the fastest in all cases. For single argument functions is it comparable to, but slightly and probably un-importantly faster than, `fast-memoize`. For multiple argument functions it is comparable to, but slightly and probably un-importantly faster than, `micro-memoize`. +The speed tests are below. In most cases `nano-memoize` is the fastest. + + * For single primitive argument functions it is comparable to, but slightly and probably un-importantly faster that `fast-memoize`. + + * For single object argument functions it is always by far the fastest. + + * For multiple primitive argument functions functions`nano-memoize` slightly and probably un-importantly faster than `fast-memoize`. -We have found that benchmarks can vary dramatically from O/S to O/S or Node version to Node version. These tests were run on a Windows 10 64bit 2.4ghz machine with 8GB RAM and Node v9.4.0. + * For multiple object argument functions `fast-memoize` slightly and probably un-importantly faster. + +We have found that benchmarks can vary dramatically from O/S to O/S or Node version to Node version. These tests were run on a Windows 10 64bit 2.4ghz machine with 8GB RAM and Node v9.4.0. Also, even with multiple samplings, garbage collection can have a substative impact and multiple runs in different orders are really required for apples-to-apples comparisons. Functions with a single primitive parameter... -| Name | Ops / sec | Relative margin of error | Sample size | -|---------------|-------------|--------------------------|-------------| -| nano-memoize | 152,526,010 | ± 2.58% | 80 | -| fast-memoize | 147,683,192 | ± 2.90% | 85 | -| micro-memoize | 22,682,348 | ± 3.69% | 75 | -| iMemoized | 22,292,411 | ± 4.47% | 72 | -| lodash | 20,937,311 | ± 1.94% | 88 | -| moize | 16,296,876 | ± 4.77% | 74 | -| memoizee | 9,651,118 | ± 3.07% | 86 | -| underscore | 9,266,277 | ± 2.66% | 75 | -| lru-memoize | 6,676,849 | ± 2.93% | 87 | -| addy-osmani | 3,899,834 | ± 2.27% | 86 | -| memoizerific | 3,753,347 | ± 2.33% | 86 | -| ramda | 493,665 | ± 1.77% | 88 | ++----------------------------------------------------------------------+ +¦ Name ¦ Ops / sec ¦ Relative margin of error ¦ Sample size ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ namo-memoize ¦ 277,174,954 ¦ ± 0.39% ¦ 94 ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ fast-memoize ¦ 243,829,313 ¦ ± 4.97% ¦ 81 ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ iMemoized ¦ 49,406,719 ¦ ± 3.90% ¦ 82 ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ micro-memoize ¦ 48,245,239 ¦ ± 2.19% ¦ 89 ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ moize ¦ 47,380,879 ¦ ± 0.59% ¦ 88 ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ lru-memoize ¦ 39,284,232 ¦ ± 4.35% ¦ 87 ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ lodash ¦ 31,464,058 ¦ ± 2.91% ¦ 91 ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ memoizee ¦ 19,406,111 ¦ ± 4.90% ¦ 79 ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ underscore ¦ 16,986,840 ¦ ± 5.83% ¦ 75 ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ addy-osmani ¦ 4,496,619 ¦ ± 0.98% ¦ 92 ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ memoizerific ¦ 2,394,952 ¦ ± 6.96% ¦ 49 ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ ramda ¦ 1,095,063 ¦ ± 2.10% ¦ 86 ¦ ++----------------------------------------------------------------------+ Functions with a single object parameter... -| Name | Ops / sec | Relative margin of error | Sample size | -|---------------|------------|--------------------------|-------------| -| nano-memoize | 53,741,011 | ± 2.06% | 85 | -| fast-memoize | 51,041,370 | ± 2.40% | 82 | -| micro-memoize | 22,638,078 | ± 3.96% | 77 | -| lodash | 22,187,376 | ± 1.72% | 83 | -| moize | 19,446,817 | ± 3.32% | 81 | -| underscore | 13,643,959 | ± 3.17% | 81 | -| iMemoized | 11,926,976 | ± 5.90% | 80 | -| memoizee | 8,010,016 | ± 1.99% | 83 | -| lru-memoize | 5,709,156 | ± 1.89% | 89 | -| memoizerific | 3,817,781 | ± 1.46% | 90 | -| addy-osmani | 3,699,956 | ± 3.30% | 85 | -| ramda | 793,756 | ± 1.92% | 87 | ++----------------------------------------------------------------------+ +¦ Name ¦ Ops / sec ¦ Relative margin of error ¦ Sample size ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ namo-memoize ¦ 271,647,146 ¦ 0.74% ¦ 90 ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ micro-memoize ¦ 44,126,430 ¦ 4.22% ¦ 81 ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ fast-memoize ¦ 44,125,722 ¦ 2.14% ¦ 82 ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ iMemoized ¦ 43,981,304 ¦ 1.61% ¦ 89 ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ moize ¦ 32,603,505 ¦ 3.19% ¦ 14 ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ lodash ¦ 31,277,037 ¦ 1.16% ¦ 88 ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ underscore ¦ 20,293,644 ¦ 1.02% ¦ 88 ¦ ++---------------+-------------+--------------------------+-------------¦ +¦ memoizee ¦ 11,533,134 ¦ 1.35% ¦ 89 ¦ ++---------------+-------------+--------------------------+-------------¦ Functions with multiple parameters that contain only primitives... -| Name | Ops / sec | Relative margin of error | Sample size | -|---------------|------------|--------------------------|-------------| -| nano-memoize | 18,408,074 | ± 1.24% | 85 | -| micro-memoize | 17,310,593 | ± 1.11% | 85 | -| moize | 14,457,697 | ± 0.79% | 90 | -| memoizee | 7,723,320 | ± 0.54% | 94 | -| iMemoized | 5,934,041 | ± 1.03% | 90 | -| lru-memoize | 5,388,273 | ± 0.52% | 94 | -| memoizerific | 3,206,479 | ± 0.30% | 93 | -| addy-osmani | 2,397,744 | ± 0.48% | 93 | -| fast-memoize | 899,483 | ± 0.37% | 91 | ++---------------------------------------------------------------------+ +¦ Name ¦ Ops / sec ¦ Relative margin of error ¦ Sample size ¦ ++---------------+------------+--------------------------+-------------¦ +¦ nano-memoize ¦ 24,739,433 ¦ 0.98% ¦ 85 ¦ ++---------------+------------+--------------------------+-------------¦ +¦ micro-memoize ¦ 23,131,341 ¦ 3.33% ¦ 74 ¦ ++---------------+------------+--------------------------+-------------¦ +¦ moize ¦ 20,241,359 ¦ 2.45% ¦ 81 ¦ ++---------------+------------+--------------------------+-------------¦ +¦ memoizee ¦ 9,917,821 ¦ 2.58% ¦ 85 ¦ ++---------------+------------+--------------------------+-------------¦ +¦ lru-memoize ¦ 7,582,999 ¦ 2.85% ¦ 82 ¦ ++---------------+------------+--------------------------+-------------¦ +¦ iMemoized ¦ 4,765,891 ¦ 12.92% ¦ 68 ¦ ++---------------+------------+--------------------------+-------------¦ +¦ memoizerific ¦ 3,200,253 ¦ 3.02% ¦ 84 ¦ ++---------------+------------+--------------------------+-------------¦ +¦ addy-osmani ¦ 2,240,692 ¦ 2.28% ¦ 87 ¦ ++---------------+------------+--------------------------+-------------¦ +¦ fast-memoize ¦ 885,271 ¦ 3.99% ¦ 82 ¦ ++---------------------------------------------------------------------+ Functions with multiple parameters that contain objects... -| Name | Ops / sec | Relative margin of error | Sample size | -|---------------|------------|--------------------------|-------------| -| nano-memoize | 13,869,690 | ± 1.25% | 86 | -| micro-memoize | 13,192,239 | ± 3.13% | 78 | -| moize | 10,895,627 | ± 3.33% | 71 | -| memoizee | 5,794,981 | ± 0.83% | 92 | -| lru-memoize | 5,148,065 | ± 0.44% | 92 | -| memoizerific | 3,206,713 | ± 0.86% | 93 | -| addy-osmani | 996,705 | ± 0.45% | 92 | -| fast-memoize | 699,597 | ± 1.17% | 91 | ++---------------------------------------------------------------------+ +¦ Name ¦ Ops / sec ¦ Relative margin of error ¦ Sample size ¦ ++---------------+------------+--------------------------+-------------¦ +¦ micro-memoize ¦ 23,846,343 ¦ 3.35% ¦ 84 ¦ ++---------------+------------+--------------------------+-------------¦ +¦ nano-memoize ¦ 17,861,879 ¦ 2.49% ¦ 84 ¦ ++---------------+------------+--------------------------+-------------¦ +¦ moize ¦ 17,147,054 ¦ 5.62% ¦ 63 ¦ ++---------------+------------+--------------------------+-------------¦ +¦ lru-memoize ¦ 7,247,819 ¦ 3.85% ¦ 81 ¦ ++---------------+------------+--------------------------+-------------¦ +¦ memoizee ¦ 6,860,227 ¦ 1.17% ¦ 88 ¦ ++---------------+------------+--------------------------+-------------¦ +¦ memoizerific ¦ 3,399,423 ¦ 2.60% ¦ 85 ¦ ++---------------+------------+--------------------------+-------------¦ +¦ addy-osmani ¦ 795,071 ¦ 1.43% ¦ 85 ¦ ++---------------+------------+--------------------------+-------------¦ +¦ fast-memoize ¦ 715,841 ¦ 1.05% ¦ 86 ¦ ++---------------------------------------------------------------------+ Deep equals ... -| Name | Ops / sec | Relative margin of error | Sample size | -|---------------------------------------------------|------------|--------------------------|-------------| -| nano-memoize deep equals (lodash isEqual) | 18,024,422 | ± 1.78% | 83 | -| micro-memoize deep equals (lodash isEqual) | 17,219,476 | ± 0.69% | 86 | -| nano-memoize deep equals (fast-equals deepEqual) | 14,732,731 | ± 3.10% | 85 | -| micro-memoize deep equals (fast-equals deepEqual) | 8,785,408 | ± 11.28% | 51 | -| micro-memoize deep equals (hash-it isEqual) | 5,744,080 | ± 10.69% | 48 | ++---------------------------------------------------------------------------------------------------------+ +¦ Name ¦ Ops / sec ¦ Relative margin of error ¦ Sample size ¦ ++---------------------------------------------------+------------+--------------------------+-------------¦ +¦ micro-memoize deep equals (lodash isEqual) ¦ 37,582,028 ¦ 1.87% ¦ 83 ¦ ++---------------------------------------------------+------------+--------------------------+-------------¦ +¦ micro-memoize deep equals (fast-equals deepEqual) ¦ 21,181,692 ¦ 6.75% ¦ 66 ¦ ++---------------------------------------------------+------------+--------------------------+-------------¦ +¦ nanomemoize deep equals (fast-equals deepEqual) ¦ 17,186,548 ¦ 3.22% ¦ 80 ¦ ++---------------------------------------------------+------------+--------------------------+-------------¦ +¦ nanomemoize deep equals (lodash isEqual) ¦ 14,995,992 ¦ 3.49% ¦ 75 ¦ ++---------------------------------------------------+------------+--------------------------+-------------¦ +¦ micro-memoize deep equals (hash-it isEqual) ¦ 14,376,860 ¦ 3.27% ¦ 78 ¦ ++---------------------------------------------------------------------------------------------------------+ We were puzzled about the multiple argument performance on `fast-memoize` given its stated goal of being the "fastest possible". We discovered that the default caching and serialization approach used by fast-memoize only performs well for single argument functions for two reasons: @@ -121,16 +172,25 @@ The shape of options is: ```javascript { - maxArgs: number, // only use the provided maxArgs for cache look-up, useful for ignoring final callback arguments - maxAge: number, // number of milliseconds to cache a result - serializer: function, // the serializer/key generator to use for single argument functions (multi-argument functions do not use a serializer) - equals: function, // the equals function to use for multi-argument functions, e.g. deepEquals for objects (single-argument functions use serializer not equals) - vargs: boolean // forces the use of multi-argument paradigm, auto set if function has a spread argument or uses `arguments` in its body. + // only use the provided maxArgs for cache look-up, useful for ignoring final callback arguments + maxArgs: number, + // number of milliseconds to cache a result + maxAge: number, + // the serializer/key generator to use for single argument functions (multi-argument functionsuse equals) + serializer: function, + // the equals function to use for multi-argument functions, e.g. deepEquals for objects (single-argument functions serializer) + equals: function, + // forces the use of multi-argument paradigm, auto set if function has a spread argument or uses `arguments` in its body. + vargs: boolean + // number of milliseconds between checks to expire memos, defaults to 1, set to 0 if you want to disable + expireInterval: number, } ``` # Release History (reverse chronological order) +2019-02-16 v1.0.1 Memo expiration optimization. Issue 4 addressed. + 2018-04-13 v1.0.0 Code style improvements. 2018-02-07 v0.1.2 Documentation updates diff --git a/benchmark/index.js b/benchmark/index.js index c989662..021b7f0 100644 --- a/benchmark/index.js +++ b/benchmark/index.js @@ -181,7 +181,7 @@ const runSingleParameterSuite = () => { .add('fast-memoize', () => { mFastMemoize(fibonacciNumber); }) - .add('namo-memoize', () => { + .add('nano-memoize', () => { mNano(fibonacciNumber); }) .on('start', () => { @@ -255,7 +255,7 @@ const runSingleParameterObjectSuite = () => { .add('fast-memoize', () => { mFastMemoize(fibonacciNumber); }) - .add('namo-memoize', () => { + .add('nano-memoize', () => { mNano(fibonacciNumber); }) .on('start', () => { diff --git a/browser/nano-memoize.js b/browser/nano-memoize.js index c0cb350..fd9b324 100644 --- a/browser/nano-memoize.js +++ b/browser/nano-memoize.js @@ -1,22 +1,22 @@ (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o { + const hsVrgs = (f) => { const s = f+"", i = s.indexOf("..."); return i>=0 && i=0); }; function nanomemoize (fn, options={}) { - // for single argument functions, just use a JS object key look-up - function single (f,s,change,serializer,arg) { + // for sngl argument functions, just use a JS object key look-up + function sngl (f,s,chng,serializer,arg) { // strings must be stringified because cache[1] should not equal or overwrite cache["1"] for value = 1 and value = "1" const key = (!arg || typeof arg === "number" || typeof arg ==="boolean" ? arg : serializer(arg)); - if(change) change(key); + if(chng) chng(key); return s[key] || ( s[key] = f.call(this, arg)); } - // for multiple arg functions, loop through a cache of all the args + // for mltpl arg functions, loop through a cache of all the args // looking at each arg separately so a test can abort as soon as possible - function multiple(f,k,v,eq,change,max=0,...args) { + function mltpl(f,k,v,eq,chng,max=0,...args) { const rslt = {}; for(let i=0;i=0 ? rslt.i : v.length; - if(change) { change(i); } + if(chng) { chng(i); } return typeof rslt.v === "undefined" ? v[i] = f.call(this,...(k[i] = args)) : rslt.v; } const { @@ -41,52 +41,47 @@ equals, maxAge, maxArgs, - vargs = hasVargs(fn) + vargs = hsVrgs(fn), + expireInterval = 1 } = options, - s = {}, // single arg function key/value cache - k = [], // multiple arg function arg key cache - v = [], // multiple arg function result cache - c = {}, // key change cache - change = (cache,key) => { // logs key changes + s = {}, // sngl arg function key/value cache + k = [], // mltpl arg function arg key cache + v = [], // mltpl arg function result cache + c = {}, // key chng cache + chng = (cache,key) => { // logs key chngs c[key] = {key,cache}; }, t = {}, - timeout = (change) => { // deletes timed-out keys - if(t[change.key]) { clearTimeout(t[change.key]); } - t[change.key] = setTimeout(() => { - delete change.cache[change.key]; - delete t[change.key]; + tmout = (chng) => { // deletes timed-out keys + if(t[chng.key]) { clearTimeout(t[chng.key]); } + t[chng.key] = setTimeout(() => { + delete chng.cache[chng.key]; + delete t[chng.key]; },maxAge); }; - setInterval(() => { // process key changes out of cycle for speed - for(let p in c) { - if(maxAge) { timeout(c[p]); } - delete c[p]; - } - },1); let f, - unary = fn.length===1 && !equals && !vargs; + unry = fn.length===1 && !equals && !vargs; // pre-bind core arguments, faster than using a closure or passing on stack or in this case using a partial - if(unary) { - f = single.bind( + if(unry) { + f = sngl.bind( this, fn, s, - (maxAge ? change.bind(this,s): null), // turn change logging on and bind to arg cache s + (maxAge ? chng.bind(this,s): null), // turn chng logging on and bind to arg cache s serializer ); } else { - f = multiple.bind( + f = mltpl.bind( this, fn, k, v, equals || ((a,b) => a===b), // default to just a regular strict comparison - (maxAge ? change.bind(this,v): null), // turn change logging on and bind to arg cache v + (maxAge ? chng.bind(this,v): null), // turn chng logging on and bind to arg cache v maxArgs ); } - // reset all the caches, must change array length or delete keys on objects to retain bind integrity + // reset all the caches, must chng array length or delete keys on objects to retain bind integrity f.clear = () => { Object.keys(s).forEach((k) => delete s[k]); k.length = 0; //k.splice(0,k.length); @@ -94,9 +89,17 @@ Object.keys(c).forEach(k => delete c[k]); Object.keys(t).forEach(k => { clearTimeout(t[k]); delete t[k]; }); }; - f.keys = () => (!unary ? k.slice() : null); - f.values = () => (!unary ? v.slice() : null); - f.keyValues = () => (unary ? Object.assign({},s) : null); + f.keys = () => (!unry ? k.slice() : null); + f.values = () => (!unry ? v.slice() : null); + f.keyValues = () => (unry ? Object.assign({},s) : null); + if(expireInterval) { + f.interval = setInterval(() => { // process key chngs out of cycle for speed + for(const p in c) { + if(maxAge) { tmout(c[p]); } + delete c[p]; + } + },expireInterval); + } return f; } diff --git a/browser/nano-memoize.min.js b/browser/nano-memoize.min.js index a506f28..b130d0e 100644 --- a/browser/nano-memoize.min.js +++ b/browser/nano-memoize.min.js @@ -1 +1 @@ -(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o{const s=f+"",i=s.indexOf("...");return i>=0&&i=0)};function nanomemoize(fn,options={}){function single(f,s,change,serializer,arg){const key=!arg||typeof arg==="number"||typeof arg==="boolean"?arg:serializer(arg);if(change)change(key);return s[key]||(s[key]=f.call(this,arg))}function multiple(f,k,v,eq,change,max=0,...args){const rslt={};for(let i=0;i=0?rslt.i:v.length;if(change){change(i)}return typeof rslt.v==="undefined"?v[i]=f.call(this,...k[i]=args):rslt.v}const{serializer:serializer=(value=>JSON.stringify(value)),equals:equals,maxAge:maxAge,maxArgs:maxArgs,vargs:vargs=hasVargs(fn)}=options,s={},k=[],v=[],c={},change=(cache,key)=>{c[key]={key:key,cache:cache}},t={},timeout=change=>{if(t[change.key]){clearTimeout(t[change.key])}t[change.key]=setTimeout(()=>{delete change.cache[change.key];delete t[change.key]},maxAge)};setInterval(()=>{for(let p in c){if(maxAge){timeout(c[p])}delete c[p]}},1);let f,unary=fn.length===1&&!equals&&!vargs;if(unary){f=single.bind(this,fn,s,maxAge?change.bind(this,s):null,serializer)}else{f=multiple.bind(this,fn,k,v,equals||((a,b)=>a===b),maxAge?change.bind(this,v):null,maxArgs)}f.clear=(()=>{Object.keys(s).forEach(k=>delete s[k]);k.length=0;v.length=0;Object.keys(c).forEach(k=>delete c[k]);Object.keys(t).forEach(k=>{clearTimeout(t[k]);delete t[k]})});f.keys=(()=>!unary?k.slice():null);f.values=(()=>!unary?v.slice():null);f.keyValues=(()=>unary?Object.assign({},s):null);return f}if(typeof module!=="undefined")module.exports=nanomemoize;if(typeof window!=="undefined")window.nanomemoize=nanomemoize}).call(this)},{}]},{},[1]); \ No newline at end of file +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o{const s=f+"",i=s.indexOf("...");return i>=0&&i=0)};function nanomemoize(fn,options={}){function sngl(f,s,chng,serializer,arg){const key=!arg||typeof arg==="number"||typeof arg==="boolean"?arg:serializer(arg);if(chng)chng(key);return s[key]||(s[key]=f.call(this,arg))}function mltpl(f,k,v,eq,chng,max=0,...args){const rslt={};for(let i=0;i=0?rslt.i:v.length;if(chng){chng(i)}return typeof rslt.v==="undefined"?v[i]=f.call(this,...k[i]=args):rslt.v}const{serializer:serializer=(value=>JSON.stringify(value)),equals:equals,maxAge:maxAge,maxArgs:maxArgs,vargs:vargs=hsVrgs(fn),expireInterval:expireInterval=1}=options,s={},k=[],v=[],c={},chng=(cache,key)=>{c[key]={key:key,cache:cache}},t={},tmout=chng=>{if(t[chng.key]){clearTimeout(t[chng.key])}t[chng.key]=setTimeout(()=>{delete chng.cache[chng.key];delete t[chng.key]},maxAge)};let f,unry=fn.length===1&&!equals&&!vargs;if(unry){f=sngl.bind(this,fn,s,maxAge?chng.bind(this,s):null,serializer)}else{f=mltpl.bind(this,fn,k,v,equals||((a,b)=>a===b),maxAge?chng.bind(this,v):null,maxArgs)}f.clear=(()=>{Object.keys(s).forEach(k=>delete s[k]);k.length=0;v.length=0;Object.keys(c).forEach(k=>delete c[k]);Object.keys(t).forEach(k=>{clearTimeout(t[k]);delete t[k]})});f.keys=(()=>!unry?k.slice():null);f.values=(()=>!unry?v.slice():null);f.keyValues=(()=>unry?Object.assign({},s):null);if(expireInterval){f.interval=setInterval(()=>{for(const p in c){if(maxAge){tmout(c[p])}delete c[p]}},expireInterval)}return f}if(typeof module!=="undefined")module.exports=nanomemoize;if(typeof window!=="undefined")window.nanomemoize=nanomemoize}).call(this)},{}]},{},[1]); \ No newline at end of file diff --git a/dist/nano-memoize.js b/dist/nano-memoize.js index 89f6738..3246dec 100644 --- a/dist/nano-memoize.js +++ b/dist/nano-memoize.js @@ -1,21 +1,21 @@ (function() { "use strict"; - const hasVargs = (f) => { + const hsVrgs = (f) => { const s = f+"", i = s.indexOf("..."); return i>=0 && i=0); }; function nanomemoize (fn, options={}) { - // for single argument functions, just use a JS object key look-up - function single (f,s,change,serializer,arg) { + // for sngl argument functions, just use a JS object key look-up + function sngl (f,s,chng,serializer,arg) { // strings must be stringified because cache[1] should not equal or overwrite cache["1"] for value = 1 and value = "1" const key = (!arg || typeof arg === "number" || typeof arg ==="boolean" ? arg : serializer(arg)); - if(change) change(key); + if(chng) chng(key); return s[key] || ( s[key] = f.call(this, arg)); } - // for multiple arg functions, loop through a cache of all the args + // for mltpl arg functions, loop through a cache of all the args // looking at each arg separately so a test can abort as soon as possible - function multiple(f,k,v,eq,change,max=0,...args) { + function mltpl(f,k,v,eq,chng,max=0,...args) { const rslt = {}; for(let i=0;i=0 ? rslt.i : v.length; - if(change) { change(i); } + if(chng) { chng(i); } return typeof rslt.v === "undefined" ? v[i] = f.call(this,...(k[i] = args)) : rslt.v; } const { @@ -40,52 +40,47 @@ equals, maxAge, maxArgs, - vargs = hasVargs(fn) + vargs = hsVrgs(fn), + expireInterval = 1 } = options, - s = {}, // single arg function key/value cache - k = [], // multiple arg function arg key cache - v = [], // multiple arg function result cache - c = {}, // key change cache - change = (cache,key) => { // logs key changes + s = {}, // sngl arg function key/value cache + k = [], // mltpl arg function arg key cache + v = [], // mltpl arg function result cache + c = {}, // key chng cache + chng = (cache,key) => { // logs key chngs c[key] = {key,cache}; }, t = {}, - timeout = (change) => { // deletes timed-out keys - if(t[change.key]) { clearTimeout(t[change.key]); } - t[change.key] = setTimeout(() => { - delete change.cache[change.key]; - delete t[change.key]; + tmout = (chng) => { // deletes timed-out keys + if(t[chng.key]) { clearTimeout(t[chng.key]); } + t[chng.key] = setTimeout(() => { + delete chng.cache[chng.key]; + delete t[chng.key]; },maxAge); }; - setInterval(() => { // process key changes out of cycle for speed - for(let p in c) { - if(maxAge) { timeout(c[p]); } - delete c[p]; - } - },1); let f, - unary = fn.length===1 && !equals && !vargs; + unry = fn.length===1 && !equals && !vargs; // pre-bind core arguments, faster than using a closure or passing on stack or in this case using a partial - if(unary) { - f = single.bind( + if(unry) { + f = sngl.bind( this, fn, s, - (maxAge ? change.bind(this,s): null), // turn change logging on and bind to arg cache s + (maxAge ? chng.bind(this,s): null), // turn chng logging on and bind to arg cache s serializer ); } else { - f = multiple.bind( + f = mltpl.bind( this, fn, k, v, equals || ((a,b) => a===b), // default to just a regular strict comparison - (maxAge ? change.bind(this,v): null), // turn change logging on and bind to arg cache v + (maxAge ? chng.bind(this,v): null), // turn chng logging on and bind to arg cache v maxArgs ); } - // reset all the caches, must change array length or delete keys on objects to retain bind integrity + // reset all the caches, must chng array length or delete keys on objects to retain bind integrity f.clear = () => { Object.keys(s).forEach((k) => delete s[k]); k.length = 0; //k.splice(0,k.length); @@ -93,9 +88,17 @@ Object.keys(c).forEach(k => delete c[k]); Object.keys(t).forEach(k => { clearTimeout(t[k]); delete t[k]; }); }; - f.keys = () => (!unary ? k.slice() : null); - f.values = () => (!unary ? v.slice() : null); - f.keyValues = () => (unary ? Object.assign({},s) : null); + f.keys = () => (!unry ? k.slice() : null); + f.values = () => (!unry ? v.slice() : null); + f.keyValues = () => (unry ? Object.assign({},s) : null); + if(expireInterval) { + f.interval = setInterval(() => { // process key chngs out of cycle for speed + for(const p in c) { + if(maxAge) { tmout(c[p]); } + delete c[p]; + } + },expireInterval); + } return f; } diff --git a/dist/nano-memoize.min.js b/dist/nano-memoize.min.js index 1533d55..825eb8c 100644 --- a/dist/nano-memoize.min.js +++ b/dist/nano-memoize.min.js @@ -1 +1 @@ -(function(){"use strict";const hasVargs=f=>{const s=f+"",i=s.indexOf("...");return i>=0&&i=0)};function nanomemoize(fn,options={}){function single(f,s,change,serializer,arg){const key=!arg||typeof arg==="number"||typeof arg==="boolean"?arg:serializer(arg);if(change)change(key);return s[key]||(s[key]=f.call(this,arg))}function multiple(f,k,v,eq,change,max=0,...args){const rslt={};for(let i=0;i=0?rslt.i:v.length;if(change){change(i)}return typeof rslt.v==="undefined"?v[i]=f.call(this,...k[i]=args):rslt.v}const{serializer:serializer=(value=>JSON.stringify(value)),equals:equals,maxAge:maxAge,maxArgs:maxArgs,vargs:vargs=hasVargs(fn)}=options,s={},k=[],v=[],c={},change=(cache,key)=>{c[key]={key:key,cache:cache}},t={},timeout=change=>{if(t[change.key]){clearTimeout(t[change.key])}t[change.key]=setTimeout(()=>{delete change.cache[change.key];delete t[change.key]},maxAge)};setInterval(()=>{for(let p in c){if(maxAge){timeout(c[p])}delete c[p]}},1);let f,unary=fn.length===1&&!equals&&!vargs;if(unary){f=single.bind(this,fn,s,maxAge?change.bind(this,s):null,serializer)}else{f=multiple.bind(this,fn,k,v,equals||((a,b)=>a===b),maxAge?change.bind(this,v):null,maxArgs)}f.clear=(()=>{Object.keys(s).forEach(k=>delete s[k]);k.length=0;v.length=0;Object.keys(c).forEach(k=>delete c[k]);Object.keys(t).forEach(k=>{clearTimeout(t[k]);delete t[k]})});f.keys=(()=>!unary?k.slice():null);f.values=(()=>!unary?v.slice():null);f.keyValues=(()=>unary?Object.assign({},s):null);return f}if(typeof module!=="undefined")module.exports=nanomemoize;if(typeof window!=="undefined")window.nanomemoize=nanomemoize}).call(this); \ No newline at end of file +(function(){"use strict";const hsVrgs=f=>{const s=f+"",i=s.indexOf("...");return i>=0&&i=0)};function nanomemoize(fn,options={}){function sngl(f,s,chng,serializer,arg){const key=!arg||typeof arg==="number"||typeof arg==="boolean"?arg:serializer(arg);if(chng)chng(key);return s[key]||(s[key]=f.call(this,arg))}function mltpl(f,k,v,eq,chng,max=0,...args){const rslt={};for(let i=0;i=0?rslt.i:v.length;if(chng){chng(i)}return typeof rslt.v==="undefined"?v[i]=f.call(this,...k[i]=args):rslt.v}const{serializer:serializer=(value=>JSON.stringify(value)),equals:equals,maxAge:maxAge,maxArgs:maxArgs,vargs:vargs=hsVrgs(fn),expireInterval:expireInterval=1}=options,s={},k=[],v=[],c={},chng=(cache,key)=>{c[key]={key:key,cache:cache}},t={},tmout=chng=>{if(t[chng.key]){clearTimeout(t[chng.key])}t[chng.key]=setTimeout(()=>{delete chng.cache[chng.key];delete t[chng.key]},maxAge)};let f,unry=fn.length===1&&!equals&&!vargs;if(unry){f=sngl.bind(this,fn,s,maxAge?chng.bind(this,s):null,serializer)}else{f=mltpl.bind(this,fn,k,v,equals||((a,b)=>a===b),maxAge?chng.bind(this,v):null,maxArgs)}f.clear=(()=>{Object.keys(s).forEach(k=>delete s[k]);k.length=0;v.length=0;Object.keys(c).forEach(k=>delete c[k]);Object.keys(t).forEach(k=>{clearTimeout(t[k]);delete t[k]})});f.keys=(()=>!unry?k.slice():null);f.values=(()=>!unry?v.slice():null);f.keyValues=(()=>unry?Object.assign({},s):null);if(expireInterval){f.interval=setInterval(()=>{for(const p in c){if(maxAge){tmout(c[p])}delete c[p]}},expireInterval)}return f}if(typeof module!=="undefined")module.exports=nanomemoize;if(typeof window!=="undefined")window.nanomemoize=nanomemoize}).call(this); \ No newline at end of file diff --git a/index.js b/index.js index 89f6738..3246dec 100644 --- a/index.js +++ b/index.js @@ -1,21 +1,21 @@ (function() { "use strict"; - const hasVargs = (f) => { + const hsVrgs = (f) => { const s = f+"", i = s.indexOf("..."); return i>=0 && i=0); }; function nanomemoize (fn, options={}) { - // for single argument functions, just use a JS object key look-up - function single (f,s,change,serializer,arg) { + // for sngl argument functions, just use a JS object key look-up + function sngl (f,s,chng,serializer,arg) { // strings must be stringified because cache[1] should not equal or overwrite cache["1"] for value = 1 and value = "1" const key = (!arg || typeof arg === "number" || typeof arg ==="boolean" ? arg : serializer(arg)); - if(change) change(key); + if(chng) chng(key); return s[key] || ( s[key] = f.call(this, arg)); } - // for multiple arg functions, loop through a cache of all the args + // for mltpl arg functions, loop through a cache of all the args // looking at each arg separately so a test can abort as soon as possible - function multiple(f,k,v,eq,change,max=0,...args) { + function mltpl(f,k,v,eq,chng,max=0,...args) { const rslt = {}; for(let i=0;i=0 ? rslt.i : v.length; - if(change) { change(i); } + if(chng) { chng(i); } return typeof rslt.v === "undefined" ? v[i] = f.call(this,...(k[i] = args)) : rslt.v; } const { @@ -40,52 +40,47 @@ equals, maxAge, maxArgs, - vargs = hasVargs(fn) + vargs = hsVrgs(fn), + expireInterval = 1 } = options, - s = {}, // single arg function key/value cache - k = [], // multiple arg function arg key cache - v = [], // multiple arg function result cache - c = {}, // key change cache - change = (cache,key) => { // logs key changes + s = {}, // sngl arg function key/value cache + k = [], // mltpl arg function arg key cache + v = [], // mltpl arg function result cache + c = {}, // key chng cache + chng = (cache,key) => { // logs key chngs c[key] = {key,cache}; }, t = {}, - timeout = (change) => { // deletes timed-out keys - if(t[change.key]) { clearTimeout(t[change.key]); } - t[change.key] = setTimeout(() => { - delete change.cache[change.key]; - delete t[change.key]; + tmout = (chng) => { // deletes timed-out keys + if(t[chng.key]) { clearTimeout(t[chng.key]); } + t[chng.key] = setTimeout(() => { + delete chng.cache[chng.key]; + delete t[chng.key]; },maxAge); }; - setInterval(() => { // process key changes out of cycle for speed - for(let p in c) { - if(maxAge) { timeout(c[p]); } - delete c[p]; - } - },1); let f, - unary = fn.length===1 && !equals && !vargs; + unry = fn.length===1 && !equals && !vargs; // pre-bind core arguments, faster than using a closure or passing on stack or in this case using a partial - if(unary) { - f = single.bind( + if(unry) { + f = sngl.bind( this, fn, s, - (maxAge ? change.bind(this,s): null), // turn change logging on and bind to arg cache s + (maxAge ? chng.bind(this,s): null), // turn chng logging on and bind to arg cache s serializer ); } else { - f = multiple.bind( + f = mltpl.bind( this, fn, k, v, equals || ((a,b) => a===b), // default to just a regular strict comparison - (maxAge ? change.bind(this,v): null), // turn change logging on and bind to arg cache v + (maxAge ? chng.bind(this,v): null), // turn chng logging on and bind to arg cache v maxArgs ); } - // reset all the caches, must change array length or delete keys on objects to retain bind integrity + // reset all the caches, must chng array length or delete keys on objects to retain bind integrity f.clear = () => { Object.keys(s).forEach((k) => delete s[k]); k.length = 0; //k.splice(0,k.length); @@ -93,9 +88,17 @@ Object.keys(c).forEach(k => delete c[k]); Object.keys(t).forEach(k => { clearTimeout(t[k]); delete t[k]; }); }; - f.keys = () => (!unary ? k.slice() : null); - f.values = () => (!unary ? v.slice() : null); - f.keyValues = () => (unary ? Object.assign({},s) : null); + f.keys = () => (!unry ? k.slice() : null); + f.values = () => (!unry ? v.slice() : null); + f.keyValues = () => (unry ? Object.assign({},s) : null); + if(expireInterval) { + f.interval = setInterval(() => { // process key chngs out of cycle for speed + for(const p in c) { + if(maxAge) { tmout(c[p]); } + delete c[p]; + } + },expireInterval); + } return f; } diff --git a/package.json b/package.json index e2e9168..c0da5a8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nano-memoize", - "version": "v1.0.0", + "version": "v1.0.1", "description": "Faster than fast, smaller than micro ... a nano speed and nano size memoizer.", "engines": {}, "license": "MIT", diff --git a/src/nano-memoize.js b/src/nano-memoize.js index 89f6738..3246dec 100644 --- a/src/nano-memoize.js +++ b/src/nano-memoize.js @@ -1,21 +1,21 @@ (function() { "use strict"; - const hasVargs = (f) => { + const hsVrgs = (f) => { const s = f+"", i = s.indexOf("..."); return i>=0 && i=0); }; function nanomemoize (fn, options={}) { - // for single argument functions, just use a JS object key look-up - function single (f,s,change,serializer,arg) { + // for sngl argument functions, just use a JS object key look-up + function sngl (f,s,chng,serializer,arg) { // strings must be stringified because cache[1] should not equal or overwrite cache["1"] for value = 1 and value = "1" const key = (!arg || typeof arg === "number" || typeof arg ==="boolean" ? arg : serializer(arg)); - if(change) change(key); + if(chng) chng(key); return s[key] || ( s[key] = f.call(this, arg)); } - // for multiple arg functions, loop through a cache of all the args + // for mltpl arg functions, loop through a cache of all the args // looking at each arg separately so a test can abort as soon as possible - function multiple(f,k,v,eq,change,max=0,...args) { + function mltpl(f,k,v,eq,chng,max=0,...args) { const rslt = {}; for(let i=0;i=0 ? rslt.i : v.length; - if(change) { change(i); } + if(chng) { chng(i); } return typeof rslt.v === "undefined" ? v[i] = f.call(this,...(k[i] = args)) : rslt.v; } const { @@ -40,52 +40,47 @@ equals, maxAge, maxArgs, - vargs = hasVargs(fn) + vargs = hsVrgs(fn), + expireInterval = 1 } = options, - s = {}, // single arg function key/value cache - k = [], // multiple arg function arg key cache - v = [], // multiple arg function result cache - c = {}, // key change cache - change = (cache,key) => { // logs key changes + s = {}, // sngl arg function key/value cache + k = [], // mltpl arg function arg key cache + v = [], // mltpl arg function result cache + c = {}, // key chng cache + chng = (cache,key) => { // logs key chngs c[key] = {key,cache}; }, t = {}, - timeout = (change) => { // deletes timed-out keys - if(t[change.key]) { clearTimeout(t[change.key]); } - t[change.key] = setTimeout(() => { - delete change.cache[change.key]; - delete t[change.key]; + tmout = (chng) => { // deletes timed-out keys + if(t[chng.key]) { clearTimeout(t[chng.key]); } + t[chng.key] = setTimeout(() => { + delete chng.cache[chng.key]; + delete t[chng.key]; },maxAge); }; - setInterval(() => { // process key changes out of cycle for speed - for(let p in c) { - if(maxAge) { timeout(c[p]); } - delete c[p]; - } - },1); let f, - unary = fn.length===1 && !equals && !vargs; + unry = fn.length===1 && !equals && !vargs; // pre-bind core arguments, faster than using a closure or passing on stack or in this case using a partial - if(unary) { - f = single.bind( + if(unry) { + f = sngl.bind( this, fn, s, - (maxAge ? change.bind(this,s): null), // turn change logging on and bind to arg cache s + (maxAge ? chng.bind(this,s): null), // turn chng logging on and bind to arg cache s serializer ); } else { - f = multiple.bind( + f = mltpl.bind( this, fn, k, v, equals || ((a,b) => a===b), // default to just a regular strict comparison - (maxAge ? change.bind(this,v): null), // turn change logging on and bind to arg cache v + (maxAge ? chng.bind(this,v): null), // turn chng logging on and bind to arg cache v maxArgs ); } - // reset all the caches, must change array length or delete keys on objects to retain bind integrity + // reset all the caches, must chng array length or delete keys on objects to retain bind integrity f.clear = () => { Object.keys(s).forEach((k) => delete s[k]); k.length = 0; //k.splice(0,k.length); @@ -93,9 +88,17 @@ Object.keys(c).forEach(k => delete c[k]); Object.keys(t).forEach(k => { clearTimeout(t[k]); delete t[k]; }); }; - f.keys = () => (!unary ? k.slice() : null); - f.values = () => (!unary ? v.slice() : null); - f.keyValues = () => (unary ? Object.assign({},s) : null); + f.keys = () => (!unry ? k.slice() : null); + f.values = () => (!unry ? v.slice() : null); + f.keyValues = () => (unry ? Object.assign({},s) : null); + if(expireInterval) { + f.interval = setInterval(() => { // process key chngs out of cycle for speed + for(const p in c) { + if(maxAge) { tmout(c[p]); } + delete c[p]; + } + },expireInterval); + } return f; }