Skip to content
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

Improve song select load and reload with large beatmap databases #29639

Merged
merged 21 commits into from
Aug 30, 2024

Conversation

peppy
Copy link
Member

@peppy peppy commented Aug 29, 2024

Supersedes and closes #29550.

Test video with 100k+ beatmaps (can provide database on request).

comparison.mp4
  • Detach is now asynchronous and global (removes ~1 s stutter / load time for song select)
  • As a result, we no longer need to preload song select. This has large benefits, including reduced memory pressure when not using song select (especially noticeable when exiting the game from song select – previously it would load another one while trying to exit the game, causing a delay in shut down by 1-5 seconds).
  • This also removes stutters seen when loading song select from places that aren't the main menu, resolving the performance part of Multiplayer song selection does not save state and is slow with a lot of maps loaded #21952.
  • This also means that we are no longer requiring to query realm twice (BeatmapCarousel used to do an initial query in load followed by a second implicit one from the initial subscription callback; we now only use the callback).
  • Initial filter operation is now asynchronous (removes ~500 ms stutter on carousel display)

Reviewer notes:

I've made quite a few (unavoidable) changes to BeatmapCarousel and SongSelect, but I stand behind them being steps-in-the-right-direction from what we had. Individual commits should hopefully make things easy to digest.

Commit dd4a110 may be a bit hard to accept. If so, this commit can be removed. But it does reduce complexity if accepted.

Really unnecessary now.
This reverts a portion of ppy#9539.

The rearrangement in `SongSelect` is required to get the initial filter
into `BeatmapCarousel` (and avoid the `FilterChanged` event firing,
causing a delayed/scheduled filter application).
The only exception to the rule here was "when screen isn't active apply
without debounce" but I'm not sure we want this. It would cause a
stutter on returning to song select and I'm not even sure this is a
common scenario.

I'd rather remove it and see if someone finds an actual case where this
is an issue.
These used to work because there was a huge blocking load operation,
which is now more asynchronous.

Note that the change made in `SongSelect` is not required, but defensive
(feels it should have been doing this the whole time).
Copy link
Collaborator

@bdach bdach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a read-through for now, haven't ran yet

osu.Game/Database/DetachedBeatmapStore.cs Outdated Show resolved Hide resolved
osu.Game/Database/DetachedBeatmapStore.cs Show resolved Hide resolved
osu.Game/Screens/Select/BeatmapCarousel.cs Show resolved Hide resolved
osu.Game/Screens/Select/BeatmapCarousel.cs Outdated Show resolved Hide resolved
The explicit detach call was removed from `updateBeatmapSet`, causing
this to occur. We could optionally add it back (it will be a noop in all
cases though).
@bdach
Copy link
Collaborator

bdach commented Aug 30, 2024

I guess the one thing about this diff is that it offloads the bulk of the load onto RAM, which can get quite sizeable:

1725020717

Granted, this is on a 234MB realm db, but 200MB is still nothing to scoff at when it comes to user machines. It's not major, but it's not spare change either.

That said it doesn't look like this is new, either, so I'm not sure cumulatively memory usage will go up, because this is how things look on master already:

1725020974

Copy link
Collaborator

@bdach bdach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested basic functionality I guess, nothing seems obviously broken.

Probably something to push out to users to see if it helps and hotfix-revert if it breaks.

@bdach bdach merged commit 4811481 into ppy:master Aug 30, 2024
13 checks passed
@peppy peppy deleted the detached-beatmap-cache branch September 5, 2024 08:34
bdach added a commit to bdach/osu that referenced this pull request Sep 8, 2024
…ficulty sort

Closes ppy#29738.

This "regressed" in ppy#29639, but if I
didn't know better, I'd go as far as saying that this looks like a .NET
bug, because the fact that PR broke it looks not sane.

The TL;DR on this is that before the pull in question, the offending
`.Contains()` check that this commit modifies was called on a
`List<BeatmapSetInfo>`. The pull changed the collection type to
`BeatmapSetInfo[]`. That said, the call is a LINQ call, so all good,
right?

Not really. First off, the default overload resolution order means that
the previous code would call `List<T>.Contains()`, and not
`Enumerable.Contains<T>()`. Then again, why would that matter? In both
cases `T` is still `BeatmapSetInfo`, right? Well... about that...

It is difficult to tell for sure what precisely is happening here,
because of what looks like runtime magic. The end *symptom* is that the
old code ended up calling `Array<BeatmapSetInfo>.IndexOf()`, and the new
code ends up calling... `Array<object>.IndexOf()`.

So while yes, `BeatmapSetInfo` implements `IEquatable` and
the expectation would be that `Equals<BeatmapSetInfo>()` should be
getting called, the type elision to `object` means that we're back to
reference equality semantics, because that's what
`EqualityComparer.Default<object>` is.

A five-minute github search across dotnet/runtime yields this:

	https://github.com/dotnet/runtime/blob/c4792a228ea36792b90f87ddf7fce2477e827822/src/coreclr/vm/array.cpp#L984-L990

Now again, if I didn't know better, I'd see that "OPTIMIZATION:"
comment, see what transpired in this scenario, and call that
optimisation invalid, because it changes semantics. But I *probably*
know that the dotnet team knows better and am probably just going to
take it for what it is, because blame on that code looks to be years
old and it's probably not a new behaviour. (I haven't tested empirically
if it is.)

Instead the fix is just to tell the `.Contains()` method to use the
correct comparer.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants