From a2b6bc9e5334e4357c10a5a6553486b2d9bd5e24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 18 Jan 2022 18:10:00 +0900 Subject: [PATCH 1/7] Add benchmark coverage of variuos methods of reading properties from realm --- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 139 +++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 osu.Game.Benchmarks/BenchmarkRealmReads.cs diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs new file mode 100644 index 0000000000..5b5bdf595d --- /dev/null +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -0,0 +1,139 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using System.Threading; +using BenchmarkDotNet.Attributes; +using osu.Framework.Testing; +using osu.Framework.Threading; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets.Osu; +using osu.Game.Tests.Resources; + +namespace osu.Game.Benchmarks +{ + public class BenchmarkRealmReads : BenchmarkTest + { + private TemporaryNativeStorage storage; + private RealmContextFactory realmFactory; + private UpdateThread updateThread; + + [Params(1, 100, 1000)] + public int ReadsPerFetch { get; set; } + + public override void SetUp() + { + storage = new TemporaryNativeStorage("realm-benchmark"); + storage.DeleteDirectory(string.Empty); + + realmFactory = new RealmContextFactory(storage, "client"); + + using (var context = realmFactory.CreateContext()) + context.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo }))); + + updateThread = new UpdateThread(() => { }, null); + updateThread.Start(); + } + + [Benchmark] + public void BenchmarkDirectPropertyRead() + { + using (var context = realmFactory.CreateContext()) + { + var beatmapSet = context.All().First(); + + for (int i = 0; i < ReadsPerFetch; i++) + { + string _ = beatmapSet.Beatmaps.First().Hash; + } + } + } + + [Benchmark] + public void BenchmarkDirectPropertyReadUpdateThread() + { + var done = new ManualResetEventSlim(); + + updateThread.Scheduler.Add(() => + { + try + { + var beatmapSet = realmFactory.Context.All().First(); + + for (int i = 0; i < ReadsPerFetch; i++) + { + string _ = beatmapSet.Beatmaps.First().Hash; + } + } + finally + { + done.Set(); + } + }); + + done.Wait(); + } + + [Benchmark] + public void BenchmarkRealmLivePropertyRead() + { + using (var context = realmFactory.CreateContext()) + { + var beatmapSet = context.All().First().ToLive(realmFactory); + + for (int i = 0; i < ReadsPerFetch; i++) + { + string _ = beatmapSet.PerformRead(b => b.Beatmaps.First().Hash); + } + } + } + + [Benchmark] + public void BenchmarkRealmLivePropertyReadUpdateThread() + { + var done = new ManualResetEventSlim(); + + updateThread.Scheduler.Add(() => + { + try + { + var beatmapSet = realmFactory.Context.All().First().ToLive(realmFactory); + + for (int i = 0; i < ReadsPerFetch; i++) + { + string _ = beatmapSet.PerformRead(b => b.Beatmaps.First().Hash); + } + } + finally + { + done.Set(); + } + }); + + done.Wait(); + } + + [Benchmark] + public void BenchmarkDetachedPropertyRead() + { + using (var context = realmFactory.CreateContext()) + { + var beatmapSet = context.All().First().Detach(); + + for (int i = 0; i < ReadsPerFetch; i++) + { + string _ = beatmapSet.Beatmaps.First().Hash; + } + } + } + + [GlobalCleanup] + public void Cleanup() + { + realmFactory?.Dispose(); + storage?.Dispose(); + updateThread?.Exit(); + } + } +} From 8ef50ff42dab85eaae680274ccd7d3b25a78de2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 17 Jan 2022 17:28:01 +0900 Subject: [PATCH 2/7] Add safety to ensure `RealmLiveUnmanaged` is not used with managed instances --- osu.Game/Database/RealmLiveUnmanaged.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Database/RealmLiveUnmanaged.cs b/osu.Game/Database/RealmLiveUnmanaged.cs index ea50ccc1ff..97f2faa656 100644 --- a/osu.Game/Database/RealmLiveUnmanaged.cs +++ b/osu.Game/Database/RealmLiveUnmanaged.cs @@ -21,6 +21,9 @@ namespace osu.Game.Database /// The realm data. public RealmLiveUnmanaged(T data) { + if (data.IsManaged) + throw new InvalidOperationException($"Cannot use {nameof(RealmLiveUnmanaged)} with managed instances"); + Value = data; } From b23f4674b12251a94fd8bb0f054adcdc60f8abf2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 12:02:18 +0900 Subject: [PATCH 3/7] Update outdated exception message Co-authored-by: Salman Ahmed --- osu.Game/Database/RealmContextFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index 888ffb1dd5..ea6a4b9636 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -72,7 +72,7 @@ namespace osu.Game.Database get { if (!ThreadSafety.IsUpdateThread) - throw new InvalidOperationException(@$"Use {nameof(createContext)} when performing realm operations from a non-update thread"); + throw new InvalidOperationException(@$"Use {nameof(Run)}/{nameof(Write)} when performing realm operations from a non-update thread"); lock (contextLock) { From 7025191fdd45767321565cf28bfbb403b29148ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 12:02:44 +0900 Subject: [PATCH 4/7] Move target field outside of `Run` usage Co-authored-by: Salman Ahmed --- .../Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index 9075dfefd4..2ee3372f80 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input List bindings = null; - realmFactory.Run(realm => bindings = realm.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).Detach()); + bindings = realmFactory.Run(realm => realm.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).Detach()); foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { From a89954d67f0b1f3b77edab3b8b8f7e71c1db1de8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 12:12:13 +0900 Subject: [PATCH 5/7] Update benchmarks in line with new structure --- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 24 ++++++++++++---------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index 5b5bdf595d..bb22fab51c 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -29,8 +29,10 @@ namespace osu.Game.Benchmarks realmFactory = new RealmContextFactory(storage, "client"); - using (var context = realmFactory.CreateContext()) - context.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo }))); + realmFactory.Run(realm => + { + realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo }))); + }); updateThread = new UpdateThread(() => { }, null); updateThread.Start(); @@ -39,15 +41,15 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkDirectPropertyRead() { - using (var context = realmFactory.CreateContext()) + realmFactory.Run(realm => { - var beatmapSet = context.All().First(); + var beatmapSet = realm.All().First(); for (int i = 0; i < ReadsPerFetch; i++) { string _ = beatmapSet.Beatmaps.First().Hash; } - } + }); } [Benchmark] @@ -78,15 +80,15 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkRealmLivePropertyRead() { - using (var context = realmFactory.CreateContext()) + realmFactory.Run(realm => { - var beatmapSet = context.All().First().ToLive(realmFactory); + var beatmapSet = realm.All().First().ToLive(realmFactory); for (int i = 0; i < ReadsPerFetch; i++) { string _ = beatmapSet.PerformRead(b => b.Beatmaps.First().Hash); } - } + }); } [Benchmark] @@ -117,15 +119,15 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkDetachedPropertyRead() { - using (var context = realmFactory.CreateContext()) + realmFactory.Run(realm => { - var beatmapSet = context.All().First().Detach(); + var beatmapSet = realm.All().First().Detach(); for (int i = 0; i < ReadsPerFetch; i++) { string _ = beatmapSet.Beatmaps.First().Hash; } - } + }); } [GlobalCleanup] From c9db0181d0611554f68e70ac4ed2da1fc96e9550 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 12:24:05 +0900 Subject: [PATCH 6/7] Attempt to fix test failures on windows due to context being held open --- osu.Game.Tests/Database/RealmLiveTests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 2f16df4624..7b1cf763d6 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -47,6 +47,11 @@ namespace osu.Game.Tests.Database liveBeatmap = beatmap.ToLive(realmFactory); }); + using (realmFactory.BlockAllOperations()) + { + // recycle realm before migrating + } + using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target")) { migratedStorage.DeleteDirectory(string.Empty); From 25dbe6b27c092e5ce992e7cb4d7663cf3e8656df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 22 Jan 2022 12:58:30 +0900 Subject: [PATCH 7/7] Fix unused null assignment --- .../Settings/Sections/Input/KeyBindingsSubsection.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index 2ee3372f80..5b8a52240e 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -34,9 +34,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input { string rulesetName = Ruleset?.ShortName; - List bindings = null; - - bindings = realmFactory.Run(realm => realm.All().Where(b => b.RulesetName == rulesetName && b.Variant == variant).Detach()); + var bindings = realmFactory.Run(realm => realm.All() + .Where(b => b.RulesetName == rulesetName && b.Variant == variant) + .Detach()); foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) {