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

Change strains list to be sorted list in StrainSkill #29990

Closed

Conversation

Givikap120
Copy link
Contributor

Part of this PR - #29482

Sorted list is implemented from scratch because inbuilt C# SortedList class is not a List but Dictionary and therefore not suitable for this task.
This is changing Summing time complexity for progressive calculation from O(n^2 * logn) to O(nlogn), significantly cutting the time of TimedDifficultyAttributes calculation.
More detailed info is written on original PR.

@peppy
Copy link
Member

peppy commented Sep 27, 2024

I'm feeling deja vu on this change...

@peppy peppy self-requested a review September 27, 2024 06:37
Copy link
Member

@peppy peppy left a comment

Choose a reason for hiding this comment

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

i dunno.

List<double> strains;

// If no saved strains - calculate them from 0, and save them after that
if (savedSortedStrains == null || savedSortedStrains.Count == 0)
Copy link
Member

Choose a reason for hiding this comment

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

The zero-count part of this check is super confusing to me. What scenario is this supposed to handle..?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This function is called whenever DifficultyValue is called
So this covers pretty much every scenario except progressive calculations, preserving the same behavior as before (just straight up sort all the strains)

Comment on lines +133 to +135
strains = peaks.OrderDescending().ToList();

savedSortedStrains = new List<double>(strains);
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason we're making two new lists here?

Copy link
Contributor Author

@Givikap120 Givikap120 Sep 27, 2024

Choose a reason for hiding this comment

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

Yes, strains is what's gonna be returned with this function and modified in OsuStrainSkill (in the diffspike nerf)
when savedSortedStrains will be potentially used in next call
This is a bit of the performance loose if next call never happens, but I think one extra array copy will not make a big difference in that case

// Otherwise - just use saved strains
else
{
strains = new List<double>(savedSortedStrains);
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason for returning a new copy of the list each time? Is it going to be modified?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

strains is going to be modified, yes

@smoogipoo
Copy link
Contributor

This is too complex for me, because it's hard to prove anything's correct. If this is the best that can be done code-wise then I prefer the existing implementation/performance.

@smoogipoo
Copy link
Contributor

I'm going to close this one here. I don't have any suggestions, sorry, just... Keep things simple?

@smoogipoo smoogipoo closed this Sep 27, 2024
@Givikap120
Copy link
Contributor Author

I'm going to close this one here. I don't have any suggestions, sorry, just... Keep things simple?

This thing was made for optimisation, it cuts execution time for Timed Attributes calculation by 90%

@smoogipoo
Copy link
Contributor

smoogipoo commented Sep 27, 2024

Maybe so, but I'm not merging optimisations at the cost of maintainability, and this is not something I'd want to maintain. Maybe there's a way you can refactor it into a custom object that's fit for this role like LimitedCapacityQueue was, but I would like to avoid (and will not merge) bitfiddling 6 different conditions like this.

@Givikap120
Copy link
Contributor Author

Givikap120 commented Sep 27, 2024

Maybe so, but I'm not merging optimisations at the cost of maintainability, and this is not something I'd want to maintain. Maybe there's a way you can refactor it into a custom object that's fit for this role like LimitedCapacityQueue was, but I would like to avoid (and will not merge) bitfiddling 6 different conditions like this.

The reason why I made 4 conditions is because:

  • I wanted normal diffcalc (not timed) to be the same (because otherwise it will be slower compared to current ver)
  • strains are not only added but edited too (specifically - last strain), so just inserting it in normal SortedList won't work

But I understand why you don't want to deal with it, as the performance issue is only really bad on graveyard 60 minutes map (2.5 minutes to wait for pp counter to init)
On ranked maps current worst-case is 15 seconds (down to 1 second with this PR)

@Givikap120 Givikap120 deleted the pp_counter_optimisation_summing branch September 27, 2024 10:59
@smoogipoo
Copy link
Contributor

smoogipoo commented Sep 27, 2024

I'm not saying the optimisations are bad or unnecessary. I'm saying the code is unmaintainable.

I don't understand where the O(n^2) part is even coming from. Is it because progressive runs this code over and over again? That's an issue for another castle - the whole progressive thing is a gigantic hack and barely an MVP, and needs to be restructured from the ground up. That much was evident even in #29989 .

Rather than hacking existing data structures, try to think about how things could be re-architected to accommodate for the new requirements.

@Givikap120
Copy link
Contributor Author

Givikap120 commented Sep 27, 2024

I'm not saying the optimisations are bad or unnecessary. I'm saying the code is unmaintainable.

I don't understand where the O(n^2) part is even coming from. Is it because progressive runs this code over and over again? That's an issue for another castle - the whole progressive thing is a gigantic hack and barely an MVP, and needs to be restructured from the ground up. That much was evident even in #29989 .

Rather than hacking existing data structures, try to think about how things could be re-architected to accommodate for the new requirements.

Sort is O(nlogn). This sort runs n times (1 time for each object). Therefore time complexity is O(n^2 * logn). This is by far the worst time complexity out of all diffcalc.
Close second is O(n^2) also in progressive calculations, for copying, calculating combo and finding the objects. The last 2 are fixed in #29989

@smoogipoo
Copy link
Contributor

This sort runs n times (1 time for each object).

Only during progressive calc right?

@Givikap120
Copy link
Contributor Author

This sort runs n times (1 time for each object).

Only during progressive calc right?

Yes

@Givikap120 Givikap120 restored the pp_counter_optimisation_summing branch October 12, 2024 15:26
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.

3 participants