-
Notifications
You must be signed in to change notification settings - Fork 672
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Performance regression in 4.1.5? #560
Comments
The internal implementation definitely changed, but all the existing tests are passing. Can you post an example project that reproduces the issue, and ideally one that shows both 4.0 and 4.1.5 at the same time? (like, added as separate dependencies and then running the same logic back-to-back or something) |
yep, i'll try it with the selectors I have in mind. |
Huh, that's very surprising. I'm assuming you didn't configure any of these selectors for a larger cache size. There's specific handling internally for a cache size of 1, and I don't see anything immediately obvious that would be an issue: function createSingletonCache(equals: EqualityFn): Cache {
let entry: Entry | undefined
return {
get(key: unknown) {
if (entry && equals(entry.key, key)) {
return entry.value
}
return NOT_FOUND
},
put(key: unknown, value: unknown) {
entry = { key, value }
},
getEntries() {
return entry ? [entry] : []
},
clear() {
entry = undefined
}
}
} |
nope, this is testing 4.1.5 as a drop in replacement. we have a few spots where we're using but even with all of the components that use that disabled, still seeing this, but i'm down to a smaller list of target selectors to see when it stops happening. |
is there anything with |
Not sure what you're asking - clarify? |
I thought I saw in the various release notes something about returning undefined explicitly for a selector and a mention of this |
Yeah, I had to switch to that |
so do we need to do something on our end to change to that value? |
No, that's purely an internal thing so that the logic above it can confirm that "the cache did not find a match for the key we're checking against". |
still isolating the issue, but from observing so far, a memory leak persisted, but got a little better every time I removed more selectors in our application from running. is there something internally that keeps track of all the selectors that is doing something to cause more memory consumption? |
Nope. Each selector is its own separate cache - there's nothing global. It kinda sounds like it's less of a "memory leak" per se, and more just "caching". It sounds as if the selectors are keeping more data references alive than they used to, and that's adding more. (I realize there's a small difference in semantics there :) ) |
relevant cached selector from re-reselect area of the code: const squareId = (_: StoreType, squareId: string, dateTimeString: string, tableId: number) =>
squareId;
const dateTimeString = (_: StoreType, squareId: string, dateTimeString: string, tableId: number) =>
dateTimeString;
const tableId = (_: StoreType, squareId: string, dateTimeString: string, tableId: number) =>
tableId;
const getSquareById = (
calendarSquaresByTableAndTime: AdditionalInventoryAndTablesWithSlots,
squareId: string,
dateTimeString: string,
tableId: number
): NormalDetailedSquare | undefined => {
const { tablesWithSlots } = calendarSquaresByTableAndTime;
if (!tablesWithSlots) {
return undefined;
}
const table = tablesWithSlots[tableId];
if (!table) {
return undefined;
}
const slot = table.slots;
if (!slot) {
return undefined;
}
const slotAtDateTime = slot[dateTimeString];
if (!slotAtDateTime) {
return undefined;
}
if (!slotAtDateTime.calendarSquaresById) {
return undefined;
}
return slotAtDateTime.calendarSquaresById[squareId];
};
export const getCalendarSquareFromCalendar = createCachedSelector(
calendarSquaresByTableAndTimeSelector,
squareId,
dateTimeString,
tableId,
getSquareById
)({
keySelector: (_: StoreType, squareId: string, dateTimeString: string, tableId: number) =>
squareId,
cacheObject: new LruObjectCache({ cacheSize: 1000 }),
}); |
getting cache hit right away with 4.0.0, but not with 4.1.5 |
oh and the selector call in the function component: const calendarSquare = useSelector(function calendarSquareUseSelector(state: StoreType) {
return getCalendarSquareFromCalendar(state, calendarSquareId, dateTimeString, tableId);
}); |
Most of that looks like Does any of this happen with just |
The internals in the 4.1.5 are reselect internals, will screenshot those shortly. will profile with just reselect way as well. |
@kelly-tock fwiw the thing that would help the most is a sandbox that specifically replicates what you're seeing here. |
totally get it. so far I have a decent sandbox, but am unable to reproduce. the app i'm working on is incredibly complex(I know we all say that), so it has been hard to diagnose thus far. |
my guess is that is a huge number of small things adding up. |
every every anonymous there in the long stack of back and forth between anonymous and memoize is in this part of reselect: in the 4.0.0 version it stops at re-reselect, guessing because its a cache hit, but I can't see why it wouldn't be a cache hit in both cases. |
looks the same with a max size option as well: export const getCalendarSquareCalendar = createSelector(
calendarSquaresByTableAndTimeSelector,
calendarSquareId,
dateTimeString,
tableId,
getCalendarSquareById,
{
memoizeOptions: {
maxSize: 1000,
},
}
); |
also tried above with the result option, using shallowEqual. same result. tried a pattern for creating a new selector instance for each component instead, similar results. |
@kelly-tock Can you put together a CodeSandbox or repo that demonstrates this? I really need something I can try to dig into and profile myself. |
and runs just night and day faster. |
I'd really like to look at this sometime in the next few days. Could you please put together a CodeSandbox or a Github repo that fully demonstrates the issue happening? That would really help me be able to investigate what's going on. (Recording a replay using https://www.replay.io/ might be an option as well.) |
I’ve been on vacation. I’ll link what I have so far, but if there’s a way to send a private profile file from chrome we can maybe do that? Our app is incredibly complex with 100s of selectors building different views of data |
You could zip it and send DM it to me over on Discord, but I really am going to need to actually dig into real running code to see what's going on myself. Doesn't have to be the full app or anything - a minimal repro would be wonderful (like, a single |
I haven't gotten it fully repro-ing, but heres what I have so far: What we do a lot of currently is receive things as arrays, and re-map them into object lookups by id. we also compute various properties from combining several data sources, and have thousands of items. when we use 4.0.0 performance is fine, but 4.1.5 makes the app completely unusable. |
let me know if you would like the profiles, as recreating this so far has been problematic. |
Thanks. Looks like you've got a full-ish app there - can you point to the specific relevant bits or summarize the steps to reproduce the issue? (and if you can narrow down the demo code to just the selector-relevant parts that might also help) And yeah, if you can attach the perf traces (zipped) that would probably be helpful too. Most likely won't have time to look at this until later this week. |
@kelly-tock : hey, if you're having trouble recreating the issue standalone, could you try using https://replay.io to record a replay of the bug on your own system? I haven't used it for real yet, but it ought to be tailor-made for this kind of a bug investigation scenario. |
will send profiles and try this out today. this is a fun one! |
@markerikson what discord are you referring to? reactiflux? I can dm you about the replay.io and profile stuff. |
can't post that link here, would need your email address to add to it if you want to dm me there. |
FYI I tried to do some analysis on this today based on the perf profiles, as well as a Replay I made of the demo app running locally. No concrete answers yet - I'll have to poke at it further. I actually did the investigation as part of a livestream - that video is up on Youtube if you want to see my process: https://www.youtube.com/watch?v=GvLGEuxV6BU I do have one question: are you using Lodash's |
I believe we do have a selector with that in our codebase that I had forgotten about. Will dig that up as well. Watching the process is super interesting thanks for doing that! |
Also, in dev tools you can hit command + f/control+f and find the function. That is why I named the function in the useAppSelector call. |
a handful of selectors are created using the method in the docs with lodash.isEqual: https://github.com/reduxjs/reselect#customize-equalitycheck-for-defaultmemoize
|
ok, interesting, as an experiment I changed it to be this:
i'm guessing this is the same as just the normal and this caused the app to behave normally again. maybe this is a clue as to where the issue is? |
Yes, that's exactly the standard behavior for Will have to poke at this further, and that's definitely an interesting clue. |
I've had some issues with re-reselect and reselect (latest version) too. Decided to drop re-reselect and create my own simple implementation on top of the new API of reselect:
Example usage:
It's very simple and might not be as advanced as re-reselect, but it does the job for me. |
Upgraded and application from 4.0.0 to 4.1.5. We are also using
re-reselect
as well asdefaultMemoize
in several places as a more general memoization solution. A screen on our app is essentially unresponsive.it seems like memoization is broken, as if I observe profiles in chrome dev tools with 4.0.0 and 4.1.5, when I click on a thing for example, all the sub items in a grid of items re-render, but in 4.0.0 this is not the case. all jest tests involving selectors are passing.
have there been any subtle pattern changes when folks have upgraded that are needed? I see some changes involving
defaultMemoize
but it seems harmless.I have also tried removing
re-reselect
and using the new built inmaxSize
config options.any insight would be much appreciated! i'm still working on narrowing down the issue, but wanted to ask. Was very surprised that it all seems to work except on one screen on our application that is very data intensive.
The text was updated successfully, but these errors were encountered: