Skip to content

Commit

Permalink
Merge pull request #640 from aryaemami59/benchmarks
Browse files Browse the repository at this point in the history
  • Loading branch information
markerikson committed Nov 28, 2023
2 parents 4314dca + 844579c commit 9ee488b
Show file tree
Hide file tree
Showing 6 changed files with 1,726 additions and 614 deletions.
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ export type FunctionType<T> = Extract<T, AnyFunction>
*/
export type ExtractReturnType<FunctionsArray extends readonly AnyFunction[]> = {
[Index in keyof FunctionsArray]: FunctionsArray[Index] extends FunctionsArray[number]
? FallbackIfUnknown<ReturnType<FunctionsArray[Index]>, any>
? FallbackIfUnknown<FallbackIfUnknown<ReturnType<FunctionsArray[Index]>, any>, any>
: never
}

Expand Down
213 changes: 213 additions & 0 deletions test/benchmarks/orderOfExecution.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import type { OutputSelector, Selector } from 'reselect'
import { createSelector, defaultMemoize } from 'reselect'
import type { Options } from 'tinybench'
import { bench } from 'vitest'
import type { RootState } from '../testUtils'
import {
countRecomputations,
expensiveComputation,
logFunctionInfo,
logSelectorRecomputations,
resetSelector,
runMultipleTimes,
setFunctionNames,
setupStore,
toggleCompleted,
toggleRead
} from '../testUtils'

describe('Less vs more computation in input selectors', () => {
const store = setupStore()
const runSelector = (selector: Selector) => {
runMultipleTimes(selector, 100, store.getState())
}
const selectorLessInInput = createSelector(
[(state: RootState) => state.todos],
todos => {
expensiveComputation()
return todos.filter(todo => todo.completed)
}
)
const selectorMoreInInput = createSelector(
[
(state: RootState) => {
expensiveComputation()
return state.todos
}
],
todos => todos.filter(todo => todo.completed)
)

const nonMemoized = countRecomputations((state: RootState) => {
expensiveComputation()
return state.todos.filter(todo => todo.completed)
})
const commonOptions: Options = {
iterations: 10,
time: 0
}
setFunctionNames({ selectorLessInInput, selectorMoreInInput, nonMemoized })
const createOptions = <S extends OutputSelector>(
selector: S,
commonOptions: Options = {}
) => {
const options: Options = {
setup: (task, mode) => {
if (mode === 'warmup') return
task.opts = {
beforeEach: () => {
store.dispatch(toggleRead(1))
},
afterAll: () => {
logSelectorRecomputations(selector)
}
}
}
}
return { ...commonOptions, ...options }
}
bench(
selectorLessInInput,
() => {
runSelector(selectorLessInInput)
},
createOptions(selectorLessInInput, commonOptions)
)
bench(
selectorMoreInInput,
() => {
runSelector(selectorMoreInInput)
},
createOptions(selectorMoreInInput, commonOptions)
)
bench(
nonMemoized,
() => {
runSelector(nonMemoized)
},
{
...commonOptions,
setup: (task, mode) => {
if (mode === 'warmup') return
nonMemoized.resetRecomputations()
task.opts = {
beforeEach: () => {
store.dispatch(toggleCompleted(1))
},
afterAll: () => {
logFunctionInfo(nonMemoized, nonMemoized.recomputations())
}
}
}
}
)
})

// This benchmark is made to test to see at what point it becomes beneficial
// to use reselect to memoize a function that is a plain field accessor.
describe('Reselect vs standalone memoization for field access', () => {
const store = setupStore()
const runSelector = (selector: Selector) => {
runMultipleTimes(selector, 1_000_000, store.getState())
}
const commonOptions: Options = {
// warmupIterations: 0,
// warmupTime: 0,
// iterations: 10,
// time: 0
}
const fieldAccessorWithReselect = createSelector(
[(state: RootState) => state.users],
users => users.appSettings
)
const fieldAccessorWithMemoize = countRecomputations(
defaultMemoize((state: RootState) => {
return state.users.appSettings
})
)
const nonMemoizedAccessor = countRecomputations(
(state: RootState) => state.users.appSettings
)

setFunctionNames({
fieldAccessorWithReselect,
fieldAccessorWithMemoize,
nonMemoizedAccessor
})
const createOptions = <S extends OutputSelector>(
selector: S,
commonOptions: Options = {}
) => {
const options: Options = {
setup: (task, mode) => {
if (mode === 'warmup') return
resetSelector(selector)
task.opts = {
beforeEach: () => {
store.dispatch(toggleCompleted(1))
},
afterAll: () => {
logSelectorRecomputations(selector)
}
}
}
}
return { ...commonOptions, ...options }
}
bench(
fieldAccessorWithReselect,
() => {
runSelector(fieldAccessorWithReselect)
},
createOptions(fieldAccessorWithReselect, commonOptions)
)
bench(
fieldAccessorWithMemoize,
() => {
runSelector(fieldAccessorWithMemoize)
},
{
...commonOptions,
setup: (task, mode) => {
if (mode === 'warmup') return
fieldAccessorWithMemoize.resetRecomputations()
fieldAccessorWithMemoize.clearCache()
task.opts = {
beforeEach: () => {
store.dispatch(toggleCompleted(1))
},
afterAll: () => {
logFunctionInfo(
fieldAccessorWithMemoize,
fieldAccessorWithMemoize.recomputations()
)
}
}
}
}
)
bench(
nonMemoizedAccessor,
() => {
runSelector(nonMemoizedAccessor)
},
{
...commonOptions,
setup: (task, mode) => {
if (mode === 'warmup') return
nonMemoizedAccessor.resetRecomputations()
task.opts = {
beforeEach: () => {
store.dispatch(toggleCompleted(1))
},
afterAll: () => {
logFunctionInfo(
nonMemoizedAccessor,
nonMemoizedAccessor.recomputations()
)
}
}
}
}
)
})
Loading

0 comments on commit 9ee488b

Please sign in to comment.