perf: optimize hitresult generation for all modes#67
perf: optimize hitresult generation for all modes#67yaowan233 wants to merge 2 commits intoMaxOhn:nextfrom
Conversation
|
Excited to look into this, will hopefully have time soon 👀 |
There was a problem hiding this comment.
For taiko, catch, and osu, you updated the bruteforce implementation which is only used in tests to validate the actual implementation. For these modes, bruteforce's speed is still acceptable so I value its simplicity over any performance improvements in tests, meaning I'd rather not apply any mathematical logic to speed up my tests. If you want to improve runtime of the actual hitresult generation in the generate_state methods, be my guest.
For mania you did implement it for the generate_state method. However, you introduced an algorithm that no longer guarantees the closest result for the specified accuracy, just that your error term is smaller than some limit. This is no suitable replacement for the current approach.
One option would be to add a HitResultPriority::Statistical variant which users would then be able to choose over the other versions. Since this is no total fix and absolute solution though, I think I'd rather not introduce this mathematical complexity. Especially considering that it relies on certain values that might change on lazer's end and will then inevitably lead to incorrect behavior here.
|
Maybe adding |
|
I do agree that hitresult generation is a bit of a mess, especially for mania. Unfortunately, the current implementation does not provide a satisfying way to extend options for the generation. I opened the |
|
That sounds great! I'll keep an eye on the |
1. Taiko & Catch (
O(N)->O(1))Optimization: Replaced the iterative loop with a direct mathematical solution.
Derivation (Taiko):
Taiko Accuracy is calculated as:
Given that$n_{remaining} = n_{300} + n_{100}$ (excluding misses), we substitute $n_{100}$ :
Multiply by$2 \cdot n_{total}$ :
Result:
We can solve for$n_{300}$ directly:
Catch (CTB) applies similar logic where applicable, solving for hit counts directly based on linear scoring relationships.
2. Osu! (
O(N^2)->O(N))Optimization: Removed the inner loop for
n100by utilizing Linear Interpolation.Derivation:
Total score (proportional to Accuracy) is a linear combination of hit counts:
When iterating the outer loop,$n_{300}$ is fixed. We also know:
Substitute$n_{50}$ into the score equation:
Result:
Since Score is strictly linear with respect to$n_{100}$ (form $y = mx + c$ ), we don't need to iterate all possible values of $n_{100}$ . We can calculate the Accuracy at the minimum possible $n_{100}$ ($n_{min}$ ) and maximum possible $n_{100}$ ($n_{max}$ ), then find the optimal $n_{100}$ via Linear Interpolation:
3. Mania (
O(N^4)->O(1))Optimization: Replaced the brute-force nested loops with a Statistical Estimation method based on the Normal Distribution.
Theory & Derivation:
Unlike other modes, Mania has 5 degrees of freedom ($n_{320}, n_{300}, \dots, n_{50}$ ) but only 2 constraints (Total Hits, Target Accuracy), making exact mathematical derivation impossible without iterating.
However, valid hit distributions are not random; they follow the player's consistency, which can be modeled as a Normal Distribution (Gaussian) centered at 0ms.
Inverse Solver:
O(N^4)) to a single scalar value (O(1), fixed 20 iterations).Discretization & Constraint Handling:
Performance Benchmarks
Benchmarks based on fuzz testing (randomized inputs):
Verification & Testing
rng_mania_hitresultsto verify Accuracy tolerance (< 0.1%) instead of exact struct equality, as the statistical distribution differs from the brute-force one (more realistic vs. mathematical min-maxing).