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

Optimize search algorithms #174

Merged
merged 27 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
062efed
Optimize search algorithms
Emik03 Jul 26, 2024
96a10a1
Fix GetCommonBpm
Emik03 Jul 26, 2024
9e5815a
Use unsafe code instead of Unsafe.As
Emik03 Jul 26, 2024
e4a1845
Make style changes
Emik03 Jul 26, 2024
bef3dbc
Revert GetCommonBpm
Emik03 Jul 26, 2024
c89ab86
Sort SVs after denormalizing/normalizing
Emik03 Jul 26, 2024
ea8ceb3
Clone lists just in case they somehow get mutated by other copies
Emik03 Jul 26, 2024
1b49e12
Improve test error messages
Emik03 Jul 26, 2024
08712b9
Make sorting fully deterministic
Emik03 Jul 26, 2024
70a99b5
Fix edgecase brought up by @WilliamQiufeng
Emik03 Jul 26, 2024
d458378
Add InsertSorted<T>, which will be used to replace Add -> Sort (slow!)
Emik03 Jul 27, 2024
34dfca2
Add missing methods
Emik03 Jul 27, 2024
3ad98f1
Use hybrid insertion algorithm
Emik03 Jul 27, 2024
823d676
Fix performance regression
Emik03 Jul 27, 2024
d3d4e8b
Revert deterministic sorting, except for HitObjects
Emik03 Jul 30, 2024
68bd572
Use stable sorting algorithms
Emik03 Jul 30, 2024
acf1fe9
Fix test
Emik03 Jul 30, 2024
bbb7334
Apply lints
Emik03 Aug 1, 2024
ff4341b
Sort by StartTime
Emik03 Aug 1, 2024
f509e30
Remove IComparable<T> implementation, use IStartTime instead
Emik03 Aug 1, 2024
156463f
Merge branch 'master' into search-alg-opt
Emik03 Aug 1, 2024
0a20d02
Use HybridSort
Emik03 Aug 1, 2024
aeba7f1
Do not use BinarySearch
Emik03 Aug 1, 2024
fc47da2
Fix concurrency issue
Emik03 Aug 1, 2024
731bacc
Merge branch 'master' into search-alg-opt
Emik03 Aug 27, 2024
cc89c86
Fix HitObjectInfo.GetTimingPoint returning null
Emik03 Aug 27, 2024
c4a6ecb
Validate only when necessary
Emik03 Aug 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 205 additions & 0 deletions Quaver.API.Tests/Quaver/Resources/stable-sorting.qua
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
AudioFile: ''
SongPreviewTime: 0
BackgroundFile: ''
MapId: -1
MapSetId: -1
Mode: Keys4
Title: ''
Artist: ''
Source: ''
Tags: ''
DifficultyName: ''
EditorLayers: []
Bookmarks:
- StartTime: 0
Note: 'first'
- StartTime: 0
Note: 'first'
- StartTime: 0
Note: 'first'
- StartTime: 0
Note: 'first'
- StartTime: 0
Note: 'first'
- StartTime: 0
Note: 'first'
- StartTime: 0
Note: 'first'
- StartTime: 0
Note: 'first'
- StartTime: 0
Note: 'first'
- StartTime: 0
Note: 'first'
- StartTime: 0
Note: 'first'
- StartTime: 0
Note: 'first'
- StartTime: 0
Note: 'second'
- StartTime: 0
Note: 'third'
- StartTime: 1000
Note: 'fourth'
- StartTime: 1000
Note: 'fifth'
- StartTime: 1000
Note: 'sixth'
CustomAudioSamples: []
SoundEffects: []
TimingPoints:
- StartTime: 0
Bpm: 6
Signature: 13646
- StartTime: 0
Bpm: 6
Signature: 13646
- StartTime: 0
Bpm: 6
Signature: 13646
- StartTime: 0
Bpm: 6
Signature: 13646
- StartTime: 0
Bpm: 6
Signature: 13646
- StartTime: 0
Bpm: 6
Signature: 13646
- StartTime: 0
Bpm: 6
Signature: 13646
- StartTime: 0
Bpm: 6
Signature: 13646
- StartTime: 0
Bpm: 6
Signature: 13646
- StartTime: 0
Bpm: 6
Signature: 13646
- StartTime: 0
Bpm: 6
Signature: 13646
- StartTime: 0
Bpm: 6
Signature: 13646
- StartTime: 0
Bpm: 4
Signature: 3
- StartTime: 0
Bpm: 12
Signature: 4
- StartTime: 1000
Bpm: 27
Signature: 246
- StartTime: 1000
Bpm: 1
Signature: 12
- StartTime: 1000
Bpm: 136
Signature: 3785
SliderVelocities:
- StartTime: 0
Multiplier: 1253
- StartTime: 0
Multiplier: 1253
- StartTime: 0
Multiplier: 1253
- StartTime: 0
Multiplier: 1253
- StartTime: 0
Multiplier: 1253
- StartTime: 0
Multiplier: 1253
- StartTime: 0
Multiplier: 1253
- StartTime: 0
Multiplier: 1253
- StartTime: 0
Multiplier: 1253
- StartTime: 0
Multiplier: 1253
- StartTime: 0
Multiplier: 1253
- StartTime: 0
Multiplier: 1253
- StartTime: 0
Multiplier: 8.4
- StartTime: 0
Multiplier: -1345
- StartTime: 1000
Multiplier: 675
- StartTime: 1000
Multiplier: 0
- StartTime: 1000
Multiplier: -13548
HitObjects:
- StartTime: 0
Lane: 4
KeySounds: []
HitSound: Normal
- StartTime: 0
Lane: 4
KeySounds: []
HitSound: Normal
- StartTime: 0
Lane: 4
KeySounds: []
HitSound: Normal
- StartTime: 0
Lane: 4
KeySounds: []
HitSound: Normal
- StartTime: 0
Lane: 4
KeySounds: []
HitSound: Normal
- StartTime: 0
Lane: 4
KeySounds: []
HitSound: Normal
- StartTime: 0
Lane: 4
KeySounds: []
HitSound: Normal
- StartTime: 0
Lane: 4
KeySounds: []
HitSound: Normal
- StartTime: 0
Lane: 4
KeySounds: []
HitSound: Normal
- StartTime: 0
Lane: 4
KeySounds: []
HitSound: Normal
- StartTime: 0
Lane: 4
KeySounds: []
HitSound: Normal
- StartTime: 0
Lane: 4
KeySounds: []
HitSound: Normal
- StartTime: 0
Lane: 3
KeySounds: []
HitSound: Normal
- StartTime: 0
Lane: 2
KeySounds: []
HitSound: Normal
- StartTime: 1000
Lane: 7
KeySounds: []
HitSound: Normal
- StartTime: 1000
Lane: 1
KeySounds: []
HitSound: Normal
- StartTime: 1000
Lane: 5
KeySounds: []
HitSound: Normal
28 changes: 23 additions & 5 deletions Quaver.API.Tests/Quaver/TestCaseQua.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
using System.IO;
using System.Linq;
using System.Text;
using Force.DeepCloner;
using Quaver.API.Enums;
using Quaver.API.Maps;
using Quaver.API.Maps.Structures;
using Xunit;
using YamlDotNet.Serialization;

namespace Quaver.API.Tests.Quaver
{
Expand Down Expand Up @@ -249,6 +251,22 @@ public void InvalidKeySoundIndex()
Assert.False(qua.IsValid());
}

[Fact]
public void StableSorting()
{
const string Q = "./Quaver/Resources/stable-sorting.qua";
var unsorted = new Deserializer().Deserialize<Qua>(File.ReadAllText(Q));
var sorted = unsorted.DeepClone();
sorted.Sort();

Assert.Equal(unsorted.TimingPoints, sorted.TimingPoints, TimingPointInfo.ByValueComparer);
Assert.Equal(unsorted.SliderVelocities, sorted.SliderVelocities, SliderVelocityInfo.ByValueComparer);
Assert.Equal(unsorted.HitObjects, sorted.HitObjects, HitObjectInfo.ByValueComparer);
Assert.Equal(unsorted.CustomAudioSamples, sorted.CustomAudioSamples, CustomAudioSampleInfo.ByValueComparer);
Assert.Equal(unsorted.EditorLayers, sorted.EditorLayers, EditorLayerInfo.ByValueComparer);
Assert.Equal(unsorted.Bookmarks, sorted.Bookmarks, BookmarkInfo.ByValueComparer);
}

[Fact]
public void SVNormalization()
{
Expand Down Expand Up @@ -282,22 +300,22 @@ public void SVNormalization()

// Check that the normalization gives the correct result.
var quaDenormalizedNormalized = quaDenormalized.WithNormalizedSVs();
Assert.True(quaDenormalizedNormalized.EqualByValue(quaNormalized));
Assert.True(quaDenormalizedNormalized.EqualByValue(quaNormalized), $"Expected {test} to normalize correctly.");

// Denormalization can move the first SV (it doesn't matter where to put the InitialScrollVelocity SV).
// So check back-and-forth instead of just denormalization.
var quaNormalizedDenormalizedNormalized = quaNormalized.WithDenormalizedSVs().WithNormalizedSVs();
Assert.True(quaNormalizedDenormalizedNormalized.EqualByValue(quaNormalized));
Assert.True(quaNormalizedDenormalizedNormalized.EqualByValue(quaNormalized), $"Expected {test} to remain the same after denormalization and subsequent normalization.");

// Check that serializing and parsing the result does not change it.
var bufferDenormalized = Encoding.UTF8.GetBytes(quaDenormalized.Serialize());
var quaDenormalized2 = Qua.Parse(bufferDenormalized, false);
Assert.True(quaDenormalized.EqualByValue(quaDenormalized2));
Assert.True(quaDenormalized.EqualByValue(quaDenormalized2), $"Expected {test} denormalized to remain the same after serialization and parsing.");

var bufferNormalized = Encoding.UTF8.GetBytes(quaNormalized.Serialize());
var quaNormalized2 = Qua.Parse(bufferNormalized, false);
Assert.True(quaNormalized.EqualByValue(quaNormalized2));
Assert.True(quaNormalized.EqualByValue(quaNormalized2), $"Expected {test} to normalized to remain the same after serialization and parsing.");
}
}
}
}
}
50 changes: 50 additions & 0 deletions Quaver.API/Helpers/ListHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* Copyright (c) 2017-2018 Swan & The Quaver Team <[email protected]>.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace Quaver.API.Helpers
{
public static class ListHelper
{
private static class Cache<T>
{
public static Converter<List<T>, T[]> Converter { get; } = typeof(List<T>)
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.FirstOrDefault(x => x.FieldType == typeof(T[])) is { } method
? CreateGetter(method)
: x => x.ToArray();

private static Converter<List<T>, T[]> CreateGetter(FieldInfo field)
{
var name = $"{field.DeclaringType?.FullName}.get_{field.Name}";
var getter = new DynamicMethod(name, typeof(T[]), new[] { typeof(List<T>) }, true);
var il = getter.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, field);
il.Emit(OpCodes.Ret);
return (Converter<List<T>, T[]>)getter.CreateDelegate(typeof(Converter<List<T>, T[]>));
}
}

/// <summary>
/// Gets the underlying <see cref="Array"/> of the <see cref="List{T}"/>.
/// </summary>
/// <remarks><para>
/// Be careful when using this method as the <see cref="List{T}"/> cannot safeguard
/// against underlying mutations or out-of-bounds reading within its capacity.
/// </para></remarks>
/// <param name="list"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T[] GetUnderlyingArray<T>(List<T> list) => Cache<T>.Converter(list);
}
}
Loading
Loading