- Notifications
You must be signed in to change notification settings - Fork0
The magic memoization for the State management. ✨🧠
License
ScriptedAlchemy/memoize-state
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Caching (aka memoization) is very powerful optimization technique - however it only makes sense when maintaining the cache itself and looking up cached results is cheaper than performing computation itself again.You don't need WASM to speed up JS
Reselect? Memoize-one? Most of memoization libraries remembers the parameters you provided, not what you did inside.Sometimes is not easy to achive high cache hit ratio. Sometimes you have tothink about how to properly dissolve computation into thememoizable parts.
I don't want to think how to use memoization, I want to use memoization!
Memoize-state is built to memoize more complex situations, even the ones which are faster to recomoute, than to deside that recalculation is not needed.Just because one cheap computation can cause a redraw/reflow/recomputation cascade for a whole application.
Lets imagine some complex function.
constfn=memoize((number,state,string)=>({result:state[string].value+number}))letfirstValue=fn(1,{value:1,otherValue :1},'value');// first callfirstValue===fn(1,{value:1,otherValue :2},'value');// "nothing" changedfirstValue===fn(1,{value:1,somethingElse:3},'value');// "nothing" changedfirstValue!==fn(2,{value:1,somethingElse:3},'value');// something important changed
Allordinal memoization libraries will drop cache each time, as longstate is different each time.More of it - they will return a unique object each time, as long the function is returning a new object each time.But not today!
Memoize-state memoizes tracks usedstate parts, using the samemagic, as you can found inMobX orimmer.It will know, that it should react only on somestate.value1 change, but notvalue2.Perfect.
Now you able just to write functions AS YOU WANT. Memoize-state will detect allreally used arguments, variables and keys, and then - react only to theright changes.
- React-memoize - magic memoization for React, componentWillReceiveProps optization, selection from context, whole SFC memoization.
- beautiful-react-redux - instant memoization for React-Redux
- your project!
memoizeState(function, options)- creates memoized variant of a function.
- Name, length (argument count), and any other own key will be transferred to memoized result
- If argument is an object - memoize will perform
proxyequalcomparison, resulting true, of you did no access any object member - If argument is not an object - memoize will compare values.
- result function will have
cacheStatisticsmethod. JFYI.
cacheSize, default 1. The size of the cache.shallowCheck, default true. Perform shallow equal between arguments.equalCheck, default true. Perform deep proxyequal comparision.strictArity, default false. Limit arguments count to the function default.nestedEquality, default true. Keep the object equality for sub-proxies.safe, default false. Activate thesafememoization mode. See below.
You know - it should be apure function, returning the same results for the same arguments.mapStateToProps, should be strict equal across the different callsmapStateToProps(state) === mapStateToProps(state)or, at least, shallow equalshallowEqual(mapStateToProps(state), mapStateToProps(state)).
Creating good memoization function, using reselect, avoiding side-effects - it could be hard. I know.
Memoize-state was created to solve this case, especially this case.
Memoize-state will track the way youUSE the state.
conststate={branch1:{...},branch2:{someKey1:1,someKey2:2}}constaFunction=(state)=>state.branch2.someKey2&&Math.random();constfastFunction=memoize(aFunction);
After the first launch memoize-state will detect the used parts of a state, and then react only for changes inside them
constresult1=fastFunction(state);// result1 - some random. 42 for exampleconstresult2=fastFunction({branch2:{someKey2:2}})// result2 - the same value! A new state is `proxyequal` to the oldconstresult3=fastFunction({branch2:{someKey2:3}})// result3 - is the NEW, at last.
- Wrap mapStateToProps by
memoize - Choose the memoization options (unsafe by default).
importmemoizefrom'memoize-state';constmapStateToProps=memoize((state,props)=>{//....});
You can use compose(flow, flowRight) to pipe result from one memoized function to another. But better to useflow
! All functions acceptsObject as input and return __Object as output.
import{memoizedFlow,memoizedFlowRight,memoizedPipe,memoizedCompose}from'memoize-state';// memoizedFlow will merge result with the current input// thus you can not import and not return all the keys// and memoization will workconstsequence=memoizedFlow([({a,b})=>({sumAB:a+b}),({a,c})=>({sumAC:a+c}),({sumAB, sumAC})=>({result:sumAB+sumAC})]);sequence({a:1,b:1,c:1})===({a:1,b:1,c:1,sumAB:2,sumAC:2,result:4})//----------------importflowfrom'lodash.flow';// You have to rethrow all the variables you might need in the future// and memoization will not properly work, as long step2 will be regenerated then you will change b// as long it depends on sumAB from step1constsequence=flow([({a,b, c})=>({sumAB:a+b, a,c}),({a,c, sumAB})=>({sumAC:a+c, sumAB}),({sumAB, sumAC})=>({result:sumAB+sumAC})]);sequence({a:1,b:1,c:1})===({result:4})
memoizedFlowis equal tomemoizedPipe, and applies functions from first to last.memoizedFlowRightis equal tomemoizedCompose, and applies functions from last to right(right).
You also could use memoize-state to double check your selectors.
import{shouldBePure}from'memoize-state';constmapStateToProps=shouldBePure((state,props)=>{//....});// then it will log all situations, when result was not shallow equal to the old one, but should.
shouldBePure will deactivate itself inproduction env. UseshallBePure if you need it always enabled.
Not all functions could besafely memoized. Just not all of them.The wrapped functionhave to be pure.
letcache=0;constfunc=(state)=>(cache||cache=state.a);constmemoizedState=memoize(func);memoizedState({a:1});// will return 1 AND fill up the cachememoizedState({a:2});// will return 1 FROM cache, and dont read anything from statememoizedState({a:3});// memoize state saw, that you dont read anything from a state.// and will ignore __ANY__ changes. __FOREVER__!
PS: this would not happened if state.a is a object. Memoize-state will understand the case, when you are returning a part of a state
It's easy to fix -memoize(func, { safe: true }), but func will becalled twice to detect internal memoization.
In case of internal memoization safe-memoize will deactivate itself.
Check performed only twice. Once on execution, and once on first cached result.In both cases wrapped function should return the "same" result.
Yes, you could.
But memoize-state could disable another underlying memoizations libraries.
Not everything is simple. Memoize-state works on copies of original object,returning the originalobject, if you have returned a copy.
That means - if you get an array.sort it and return result - you will return unsorted result.
Input has to be immutable, don't sort it, don't mutate it, don't forget to Array.slice().but you are the right person to watch over it.
UsesES6 Proxy underneath to detect used branches of a state (asMobX).Removes all the magic from result value.Should be slower than "manual" __reselect__ors, but faster than anything else.
We have a performance test, according to the results -
- memoize-stateis not slower than major competitors, and10-100x times faster, for the "state" cases.
- lodash.memoize and fast-memoize could not handlebig states as input.
- memoize-one should be super fast, but it is not
But the major difference is
- memoize-one are havinghighest hitratio, than means - it were able to "memoize" most of the cases
function of 3 arguments, all unchangedmemoize-one x 6563316 ops/sec ±1.50% (6 runs sampled) hitratio 100% 2 /2432667lodash.memoize x 2988552 ops/sec ±3.98% (5 runs sampled) hitratio 100% 1 /3844342fast-memoize x 1007010 ops/sec ±1.18% (6 runs sampled) hitratio 100% 1 /4443629memoize-state x 3753869 ops/sec ±1.33% (6 runs sampled) hitratio 100% 1 /6175599Fastest is memoize-onefunction of 2 arguments, providing 3, all unchangedmemoize-one x 6077327 ops/sec ±1.53% (6 runs sampled) hitratio 100% 2 /2534824lodash.memoize x 2780103 ops/sec ±1.88% (6 runs sampled) hitratio 100% 1 /3615601fast-memoize x 928385 ops/sec ±4.59% (6 runs sampled) hitratio 100% 1 /3998562memoize-state x 3389800 ops/sec ±1.51% (6 runs sampled) hitratio 100% 1 /5243823Fastest is memoize-onefunction of 3 arguments, all changed / 10memoize-one x 19043 ops/sec ±1.15% (6 runs sampled) hitratio 50% 4083 /8163lodash.memoize x 30242 ops/sec ±1.45% (5 runs sampled) hitratio 79% 6114 /28541fast-memoize x 17442 ops/sec ±0.78% (6 runs sampled) hitratio 92% 2891 /34322memoize-state x 16621 ops/sec ±2.16% (6 runs sampled) hitratio 92% 3225 /40772Fastest is lodash.memoizefunction with an object as argument, returning a partmemoize-one x 8822 ops/sec ±1.39% (5 runs sampled) hitratio 0% 4337 /4337lodash.memoize x 1378671 ops/sec ±8.92% (6 runs sampled) hitratio 100% 1 /669631fast-memoize x 1027750 ops/sec ±6.03% (6 runs sampled) hitratio 100% 1 /1246719memoize-state x 1207975 ops/sec ±2.08% (6 runs sampled) hitratio 100% 1 /1805336Fastest is lodash.memoizefunction with an object as argument, changing value, returning a partmemoize-one x 8236 ops/sec ±1.54% (6 runs sampled) hitratio 0% 4112 /4112lodash.memoize x 74548 ops/sec ±4.14% (6 runs sampled) hitratio 91% 4106 /45160fast-memoize x 71851 ops/sec ±2.60% (6 runs sampled) hitratio 96% 3524 /80393memoize-state x 61650 ops/sec ±1.28% (6 runs sampled) hitratio 98% 2632 /106706Fastest is lodash.memoize,fast-memoizefunction with an object as argument, changing other value, returning a partmemoize-one x 7683 ops/sec ±1.78% (6 runs sampled) hitratio 0% 3488 /3488lodash.memoize x 69976 ops/sec ±2.08% (6 runs sampled) hitratio 91% 3086 /34339fast-memoize x 66844 ops/sec ±2.26% (6 runs sampled) hitratio 96% 2308 /57408memoize-state x 1085455 ops/sec ±2.39% (6 runs sampled) hitratio 100% 1 /399267Fastest is memoize-statefunction with 2 objects as argument, changing both valuememoize-one x 7251 ops/sec ±7.62% (6 runs sampled) hitratio 0% 3263 /3263lodash.memoize x 7255 ops/sec ±3.94% (5 runs sampled) hitratio 56% 2591 /5855fast-memoize x 7197 ops/sec ±2.57% (6 runs sampled) hitratio 64% 3341 /9197memoize-state x 45612 ops/sec ±5.28% (6 runs sampled) hitratio 94% 1487 /24063Fastest is memoize-statewhen changes anything, except the function gonna to consumememoize-one x 8003 ops/sec ±0.99% (5 runs sampled) hitratio 0% 3453 /3453lodash.memoize x 7640 ops/sec ±2.47% (6 runs sampled) hitratio 50% 3490 /6944fast-memoize x 7315 ops/sec ±1.38% (5 runs sampled) hitratio 73% 2576 /9521memoize-state x 461062 ops/sec ±37.16% (6 runs sampled) hitratio 100% 1 /270082Fastest is memoize-statewhen state is very big, and you need a small partmemoize-one x 7713 ops/sec ±2.21% (6 runs sampled) hitratio 0% 3518 /3518lodash.memoize x 198 ops/sec ±2.48% (6 runs sampled) hitratio 100% 10 /3613fast-memoize x 201 ops/sec ±1.54% (6 runs sampled) hitratio 100% 9 /3688memoize-state x 61350 ops/sec ±2.49% (6 runs sampled) hitratio 91% 3072 /34404Fastest is memoize-statefunctionfn1(object){returnobject.value}// ^^ memoize state will react to any change of .valuefunctionfn2(object){return{...object.value}}// ^^ memoize state will react to any change of the values inside the .value// for example, if value contain booleans the X and they Y - they form 4 possible pairsconstsuperMemoize=memoize(fn2,{cacheSize:4});// ^^ you just got uber function, which will return 4 exactly the same objects
Executing the function against EMPTY function, but triggering most of internal mechanics.
base x 244.000.431 memoize-one x 18.150.966 lodash.memoize x 3.941.183 fast-memoize x 34.699.858 memoize-state x 4.615.104this 4 millions operations per second? A bit more that enough
Memoize-state is not a best fit for a common case. It is designed to handle
- the complex objects
- limited count of stored cache lines (default: 1)
This is a fibonacci test from - fast-memoize. The test uses different performance measuring tooland numbers differs.
│ fast-memoize@current │ 204,819,529 │ ± 0.85% │ 88 │├────────────────────────────┼─────────────┼──────────────────────────┼─────────────┤│ lru-memoize (single cache) │ 84,862,416 │ ± 0.59% │ 93 │├────────────────────────────┼─────────────┼──────────────────────────┼─────────────┤│ iMemoized │ 35,008,566 │ ± 1.29% │ 90 │├────────────────────────────┼─────────────┼──────────────────────────┼─────────────┤│ lodash │ 24,197,907 │ ± 3.70% │ 82 │├────────────────────────────┼─────────────┼──────────────────────────┼─────────────┤│ underscore │ 17,308,464 │ ± 2.79% │ 87 │├────────────────────────────┼─────────────┼──────────────────────────┼─────────────┤│ memoize-state <<---- │ 17,175,290 │ ± 0.80% │ 87 │├────────────────────────────┼─────────────┼──────────────────────────┼─────────────┤│ memoizee │ 12,908,819 │ ± 2.60% │ 78 │├────────────────────────────┼─────────────┼──────────────────────────┼─────────────┤│ lru-memoize (with limit) │ 9,357,237 │ ± 0.47% │ 91 │├────────────────────────────┼─────────────┼──────────────────────────┼─────────────┤│ ramda │ 1,323,820 │ ± 0.54% │ 92 │├────────────────────────────┼─────────────┼──────────────────────────┼─────────────┤│ vanilla │ 122,835 │ ± 0.72% │ 89 │└────────────────────────────┴─────────────┴──────────────────────────┴─────────────┘memoize-state is comparable with lodash and underscore, even in this example.
memoize-state: object spread detected in XXX. Consider refactoring.
Memoize state could not properly work if you "spread" state
constmapStateToProps=({prop,i,need,...rest})=>....//orconstmapStateToProps=(state,props)=>({ ...state, ...props})//orconstmapState=({ page, direction, ...state})=>({ page, direction,isLoading:isLoading(state)})
It will assume, that you need ALL the keys, meanwhile - you could not.
Workaround - refactor the code
constmapState=state=>({page:state.page,direction:state.direction,isLoading:isLoading(state)})
Seeissue for more details
IE11/Android compatible. Containsproxy-polyfill inside.
MIT
About
The magic memoization for the State management. ✨🧠
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Languages
- JavaScript100.0%
