Skip to content

Instantly share code, notes, and snippets.

@luqmaan
Last active July 17, 2017 23:32
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save luqmaan/ed596266828847e27657619eae646328 to your computer and use it in GitHub Desktop.
Save luqmaan/ed596266828847e27657619eae646328 to your computer and use it in GitHub Desktop.
debug reselect performance

Debugging reselect performance

Some steps I'm taking to debug bad performance with reselect.

My performance problems are coming from the fact that I am not using reselect properly. I wrote a bunch of selectors that use props but don't do anything to make sure they can be shared across multiple components.

The fix for this is: https://github.com/reactjs/reselect/blob/fec65b63b9a2ffa570348271138d20cf831e0185/README.md#sharing-selectors-with-props-across-multiple-components.

But before rewriting tons of code, I'd like to see which selectors are recomputed the most.

Log re-renders

https://github.com/garbles/why-did-you-update

Add logging to the selectors

  1. Use https://github.com/ordoro/babel-transform-reselect-selector-name

  2. Update the reselect code to accept name as the first argument and to log it. See the reselect.js code below. Use babelify and overwrite node_modules/reselect/lib/index.js.

  3. Log selector recomputations

table = SelectorRegistry.map(selector => [selector.id, selector.recomputations()]).filter(x => x[1] > 0).sort((a,b) => a[1] - b[1]); table.length && console.table(table); SelectorRegistry.map(selector => selector.resetRecomputations());

image

  1. Update babelrc to contain /Users/luqmaan/dev/babel-reselect-name-transform/lib/index.js
window.SelectorRegistry = [];
function defaultEqualityCheck(a, b) {
return a === b
}
export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
let lastArgs = null
let lastResult = null
return (...args) => {
if (
lastArgs !== null &&
lastArgs.length === args.length &&
args.every((value, index) => equalityCheck(value, lastArgs[index]))
) {
return lastResult
}
lastArgs = args
lastResult = func(...args)
return lastResult
}
}
function getDependencies(funcs) {
const dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs
if (!dependencies.every(dep => typeof dep === 'function')) {
const dependencyTypes = dependencies.map(
dep => typeof dep
).join(', ')
throw new Error(
`Selector creators expect all input-selectors to be functions, ` +
`instead received the following types: [${dependencyTypes}]`
)
}
return dependencies
}
export function createSelectorCreator(id, memoize, ...memoizeOptions) {
return (...funcs) => {
let recomputations = 0
const resultFunc = funcs.pop()
const dependencies = getDependencies(funcs)
const memoizedResultFunc = memoize(
(...args) => {
recomputations++
return resultFunc(...args)
},
...memoizeOptions
)
const selector = (state, props, ...args) => {
const params = dependencies.map(
dependency => dependency(state, props, ...args)
)
return memoizedResultFunc(...params)
}
selector.resultFunc = resultFunc
selector.recomputations = () => recomputations
selector.resetRecomputations = () => recomputations = 0
selector.id = id;
SelectorRegistry.push(selector);
return selector
}
}
export function createSelector(name, ...args) {
return createSelectorCreator(name, defaultMemoize)(...args)
}
export function createStructuredSelector(selectors, selectorCreator = createSelector) {
if (typeof selectors !== 'object') {
throw new Error(
`createStructuredSelector expects first argument to be an object ` +
`where each property is a selector, instead received a ${typeof selectors}`
)
}
const objectKeys = Object.keys(selectors)
return selectorCreator(
'structured',
objectKeys.map(key => selectors[key]),
(...values) => {
return values.reduce((composition, value, index) => {
composition[objectKeys[index]] = value
return composition
}, {})
}
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment