Skip to content

Commit

Permalink
Memo expiration optimization. Issue 4 addressed.
Browse files Browse the repository at this point in the history
  • Loading branch information
anywhichway committed Feb 16, 2019
1 parent 119c237 commit a5d42ae
Show file tree
Hide file tree
Showing 9 changed files with 276 additions and 204 deletions.
186 changes: 123 additions & 63 deletions README.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions benchmark/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ const runSingleParameterSuite = () => {
.add('fast-memoize', () => {
mFastMemoize(fibonacciNumber);
})
.add('namo-memoize', () => {
.add('nano-memoize', () => {
mNano(fibonacciNumber);
})
.on('start', () => {
Expand Down Expand Up @@ -255,7 +255,7 @@ const runSingleParameterObjectSuite = () => {
.add('fast-memoize', () => {
mFastMemoize(fibonacciNumber);
})
.add('namo-memoize', () => {
.add('nano-memoize', () => {
mNano(fibonacciNumber);
})
.on('start', () => {
Expand Down
71 changes: 37 additions & 34 deletions browser/nano-memoize.js
Original file line number Diff line number Diff line change
@@ -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<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
(function() {
"use strict";
const hasVargs = (f) => {
const hsVrgs = (f) => {
const s = f+"",
i = s.indexOf("...");
return i>=0 && i<s.indexOf(")" || s.indexOf("arguments")>=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<k.length;i++) { // an array of arrays of args
let key = k[i];
Expand All @@ -33,70 +33,73 @@
}
}
const i = rslt.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 {
serializer = (value) => JSON.stringify(value),
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);
v.length = 0; //v.splice(0,v.length);
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;
}

Expand Down
2 changes: 1 addition & 1 deletion browser/nano-memoize.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 37 additions & 34 deletions dist/nano-memoize.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
(function() {
"use strict";
const hasVargs = (f) => {
const hsVrgs = (f) => {
const s = f+"",
i = s.indexOf("...");
return i>=0 && i<s.indexOf(")" || s.indexOf("arguments")>=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<k.length;i++) { // an array of arrays of args
let key = k[i];
Expand All @@ -32,70 +32,73 @@
}
}
const i = rslt.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 {
serializer = (value) => JSON.stringify(value),
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);
v.length = 0; //v.splice(0,v.length);
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;
}

Expand Down
2 changes: 1 addition & 1 deletion dist/nano-memoize.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a5d42ae

Please sign in to comment.