-
As we begin to introduce more usage of Calling
Even when there's a lot of objects in a realm, my expectation is that since realm is aware of all ongoing operations, reporting said operations to a subscription should be of negligible cost, else it renders subscriptions useless for a large number of use cases. Real world implicationsWith no subscriptions, this is how deleting items from realm looks. 20220120.185136.osu.mp4To show the overhead of a single deletion operation, I added a sleep between deletions (else the game basically comes to a halt). With one subscription and deleting a single object from our realm, we see spikes of around 500ms: 20220119.181237_osu.mp4.movWith five subscriptions (some are only subscribed on the song select screen, which explains the increased overhead compared to the menu above): 20220119.181442_osu.mp4.movThe overhead looks to be native, as checked on macOS, then windows to confirm: From the profiling output, I'd say this is likely a ReproductionI've written a barebones repro of this taking away the complexities of our project: using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using NUnit.Framework;
using Realms;
namespace RealmPerformanceTest
{
[TestFixture]
public class RealmScratch
{
private readonly Stopwatch timer = new Stopwatch();
[TestCase(1000)]
[TestCase(10000)]
[TestCase(100000)]
[TestCase(1000000)]
public void TestNestedContexts(int count)
{
ManualResetEventSlim gotInitialCallback = new ManualResetEventSlim();
timer.Restart();
string realmFilename = Path.Combine(Environment.CurrentDirectory, "test.realm");
File.Delete(realmFilename);
var configuration = new RealmConfiguration(realmFilename);
using (var realm = Realm.GetInstance(configuration))
{
using (var transaction = realm.BeginWrite())
{
for (int i = 0; i < count; i++)
realm.Add(new PersistedObject());
transaction.Commit();
}
}
Console.WriteLine($"Running with realm size {new FileInfo(realmFilename).Length:g} ({count} objects)");
using (var realm = Realm.GetInstance(configuration))
{
realm
.All<PersistedObject>()
.SubscribeForNotifications(NotifyCallback);
while (!gotInitialCallback.IsSet)
TimedOp(realm.Refresh, "refresh");
for (int i = 0; i < 10; i++)
{
using (var transaction = realm.BeginWrite())
{
// make an arbitrary change to one object
realm.All<PersistedObject>().First(s => !s.ArbitraryBool).ArbitraryBool = true;
TimedOp(transaction.Commit, "commit");
}
TimedOp(realm.Refresh, "refresh");
}
}
void NotifyCallback(IRealmCollection<PersistedObject> sender, ChangeSet changes, Exception error)
{
if (changes == null)
gotInitialCallback.Set();
}
}
private void TimedOp(Func<bool> op, string name)
{
long before = timer.ElapsedMilliseconds;
op();
Console.WriteLine($"{name} took {timer.ElapsedMilliseconds - before} ms");
}
private void TimedOp(Action op, string name)
{
long before = timer.ElapsedMilliseconds;
op();
Console.WriteLine($"{name} took {timer.ElapsedMilliseconds - before} ms");
}
public class PersistedObject : RealmObject
{
public bool ArbitraryBool { get; set; }
}
}
} Results for these runs are as follows: Running with realm size 8192 (1000 objects)
refresh took 3 ms
commit took 0 ms
refresh took 6 ms
commit took 0 ms
refresh took 0 ms
commit took 1 ms
refresh took 0 ms
commit took 0 ms
refresh took 0 ms
commit took 0 ms
refresh took 0 ms
commit took 0 ms
refresh took 1 ms
commit took 0 ms
refresh took 0 ms
commit took 0 ms
refresh took 1 ms
commit took 0 ms
refresh took 0 ms
commit took 1 ms
refresh took 0 ms Running with realm size 53248 (10000 objects)
refresh took 1 ms
commit took 0 ms
refresh took 2 ms
commit took 1 ms
refresh took 3 ms
commit took 0 ms
refresh took 3 ms
commit took 0 ms
refresh took 3 ms
commit took 0 ms
refresh took 2 ms
commit took 0 ms
refresh took 3 ms
commit took 0 ms
refresh took 2 ms
commit took 0 ms
refresh took 2 ms
commit took 0 ms
refresh took 2 ms
commit took 0 ms
refresh took 2 ms Running with realm size 512000 (100000 objects)
refresh took 15 ms
commit took 0 ms
refresh took 21 ms
commit took 22 ms
refresh took 21 ms
commit took 0 ms
refresh took 22 ms
commit took 1 ms
refresh took 22 ms
commit took 0 ms
refresh took 19 ms
commit took 1 ms
refresh took 19 ms
commit took 0 ms
refresh took 25 ms
commit took 0 ms
refresh took 21 ms
commit took 1 ms
refresh took 22 ms
commit took 1 ms
refresh took 21 ms Running with realm size 5603328 (1000000 objects)
refresh took 154 ms
commit took 1 ms
refresh took 207 ms
commit took 1 ms
refresh took 194 ms
commit took 1 ms
refresh took 192 ms
commit took 1 ms
refresh took 186 ms
commit took 2 ms
refresh took 196 ms
commit took 1 ms
refresh took 181 ms
commit took 1 ms
refresh took 187 ms
commit took 1 ms
refresh took 186 ms
commit took 1 ms
refresh took 187 ms
commit took 2 ms
refresh took 186 ms |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 8 replies
-
This is because |
Beta Was this translation helpful? Give feedback.
This is because
Refresh()
will block until all subscription handlers have fired. Not only that, but it also forces the otherwise asynchronous changeset calculation to happen synchronously. This is to ensure correctness when notifications are delivered (i.e. they need to see the Realm in a state that is consistent with the changeset indices). I don't know how feasible this is, but the most obvious approach to alleviate this is to move away from callingRefresh
manually and instead let Realm refresh itself. For this to work though, you'll need to install a synchronization context on your main thread, which may have unintended consequences for other parts of your code (e.g. async continuatio…