Skip to content

Commit 67df0ce

Browse files
authored
fix: RxObject memory allocation for change delays (#8)
1 parent 7ed71f9 commit 67df0ce

File tree

2 files changed

+38
-30
lines changed

2 files changed

+38
-30
lines changed
Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
## RxObject
22

3-
| Method | CreateNumber | Mean | Error | StdDev | Median | Gen 0 | Gen 1 | Gen 2 | Allocated |
4-
|------------------- |------------- |---------------:|--------------:|--------------:|---------------:|---------:|---------:|------:|----------:|
5-
| RxObjectCreation | 1 | 564.8 ns | 10.96 ns | 14.25 ns | 566.5 ns | 0.2022 | 0.0010 | - | 2 KB |
6-
| RxObjectWithChange | 1 | 647.4 ns | 9.88 ns | 8.76 ns | 647.0 ns | 0.2079 | 0.0010 | - | 2 KB |
7-
| RxObjectCreation | 100 | 50,249.3 ns | 917.04 ns | 857.80 ns | 50,184.9 ns | 18.5547 | 5.7983 | - | 152 KB |
8-
| RxObjectWithChange | 100 | 56,502.8 ns | 911.23 ns | 852.37 ns | 56,459.0 ns | 19.1040 | 5.1270 | - | 156 KB |
9-
| RxObjectCreation | 4000 | 5,593,739.2 ns | 69,053.41 ns | 64,592.60 ns | 5,600,920.0 ns | 742.1875 | 367.1875 | - | 6,063 KB |
10-
| RxObjectWithChange | 4000 | 6,234,991.0 ns | 122,895.87 ns | 224,722.04 ns | 6,133,371.3 ns | 757.8125 | 375.0000 | - | 6,250 KB |
3+
| Method | CreateNumber | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
4+
|------------------- |------------- |---------------:|-------------:|-------------:|---------:|---------:|------:|------------:|
5+
| RxObjectCreation | 1 | 206.5 ns | 2.15 ns | 2.01 ns | 0.0782 | - | - | 656 B |
6+
| RxObjectWithChange | 1 | 280.2 ns | 5.19 ns | 4.85 ns | 0.0839 | - | - | 704 B |
7+
| RxObjectCreation | 100 | 12,782.4 ns | 212.66 ns | 188.52 ns | 6.1340 | 0.9308 | - | 51,344 B |
8+
| RxObjectWithChange | 100 | 17,117.2 ns | 318.94 ns | 282.74 ns | 6.6833 | 0.9155 | - | 56,144 B |
9+
| RxObjectCreation | 4000 | 904,746.9 ns | 15,505.56 ns | 13,745.27 ns | 244.1406 | 112.3047 | - | 2,048,154 B |
10+
| RxObjectWithChange | 4000 | 1,269,486.4 ns | 24,327.69 ns | 22,756.13 ns | 267.5781 | 132.8125 | - | 2,240,152 B |
1111

1212

1313
## ReactiveUI ReactiveObject
14+
1415
| Method | CreateNumber | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
1516
|------------------------- |------------- |----------------:|--------------:|--------------:|---------:|---------:|---------:|------------:|
16-
| ReactiveObjectCreation | 1 | 242.4 ns | 4.57 ns | 6.26 ns | 0.1068 | - | - | 896 B |
17-
| ReactiveObjectWithChange | 1 | 1,536.5 ns | 29.92 ns | 42.91 ns | 0.2575 | 0.0057 | 0.0019 | 2,157 B |
18-
| ReactiveObjectCreation | 100 | 16,884.4 ns | 335.55 ns | 560.64 ns | 9.0027 | 1.7090 | - | 75,344 B |
19-
| ReactiveObjectWithChange | 100 | 133,628.0 ns | 2,625.84 ns | 4,598.94 ns | 24.4141 | 1.7090 | 0.4883 | 203,267 B |
20-
| ReactiveObjectCreation | 4000 | 1,579,677.4 ns | 30,944.54 ns | 34,394.78 ns | 359.3750 | 179.6875 | - | 3,008,163 B |
21-
| ReactiveObjectWithChange | 4000 | 11,459,659.3 ns | 227,605.87 ns | 212,902.68 ns | 953.1250 | 468.7500 | 218.7500 | 8,091,142 B |
17+
| ReactiveObjectCreation | 1 | 238.8 ns | 3.27 ns | 3.06 ns | 0.1068 | - | - | 896 B |
18+
| ReactiveObjectWithChange | 1 | 1,530.3 ns | 30.56 ns | 40.79 ns | 0.2575 | 0.0057 | 0.0019 | 2,157 B |
19+
| ReactiveObjectCreation | 100 | 15,796.5 ns | 314.80 ns | 398.12 ns | 9.0027 | 1.7090 | - | 75,344 B |
20+
| ReactiveObjectWithChange | 100 | 132,292.6 ns | 2,553.90 ns | 3,580.21 ns | 24.1699 | 1.9531 | 0.7324 | 203,267 B |
21+
| ReactiveObjectCreation | 4000 | 1,530,059.8 ns | 26,466.29 ns | 24,756.59 ns | 359.3750 | 179.6875 | - | 3,008,145 B |
22+
| ReactiveObjectWithChange | 4000 | 11,304,993.4 ns | 146,890.54 ns | 137,401.50 ns | 953.1250 | 468.7500 | 218.7500 | 8,091,139 B |

src/ReactiveMarbles.Mvvm/RxObject.cs

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System;
66
using System.Collections.Generic;
77
using System.ComponentModel;
8+
using System.Diagnostics.CodeAnalysis;
89
using System.Linq;
910
using System.Reactive.Disposables;
1011
using System.Reactive.Linq;
@@ -22,11 +23,8 @@ namespace ReactiveMarbles.Core
2223
/// </summary>
2324
public class RxObject : IRxObject
2425
{
25-
private readonly SourceList<RxPropertyChangedEventArgs<IRxObject>> _propertyChangedEvents = new();
26-
private readonly SourceList<RxPropertyChangingEventArgs<IRxObject>> _propertyChangingEvents = new();
2726
private readonly Lazy<Subject<Exception>> _thrownExceptions = new(() => new Subject<Exception>(), LazyThreadSafetyMode.PublicationOnly);
28-
private long _changeNotificationsDelayed;
29-
private long _changeNotificationsSuppressed;
27+
private readonly Lazy<Notifications> _notification = new(() => new Notifications());
3028

3129
/// <summary>
3230
/// Initializes a new instance of the <see cref="RxObject"/> class.
@@ -68,39 +66,39 @@ void Handler(object sender, PropertyChangingEventArgs args) =>
6866
public IObservable<RxPropertyChangedEventArgs<IRxObject>> Changed { get; }
6967

7068
/// <inheritdoc/>
71-
public bool AreChangeNotificationsEnabled() => Interlocked.Read(ref _changeNotificationsSuppressed) == 0;
69+
public bool AreChangeNotificationsEnabled() => !_notification.IsValueCreated || Interlocked.Read(ref _notification.Value.ChangeNotificationsSuppressed) == 0;
7270

7371
/// <inheritdoc/>
74-
public bool AreChangeNotificationsDelayed() => Interlocked.Read(ref _changeNotificationsDelayed) > 0;
72+
public bool AreChangeNotificationsDelayed() => _notification.IsValueCreated && Interlocked.Read(ref _notification.Value.ChangeNotificationsDelayed) > 0;
7573

7674
/// <inheritdoc/>
7775
public IDisposable SuppressChangeNotifications()
7876
{
79-
Interlocked.Increment(ref _changeNotificationsSuppressed);
80-
return Disposable.Create(() => Interlocked.Decrement(ref _changeNotificationsSuppressed));
77+
Interlocked.Increment(ref _notification.Value.ChangeNotificationsSuppressed);
78+
return Disposable.Create(() => Interlocked.Decrement(ref _notification.Value.ChangeNotificationsSuppressed));
8179
}
8280

8381
/// <inheritdoc/>
8482
public IDisposable DelayChangeNotifications()
8583
{
86-
Interlocked.Increment(ref _changeNotificationsDelayed);
84+
Interlocked.Increment(ref _notification.Value.ChangeNotificationsDelayed);
8785

8886
return Disposable.Create(() =>
8987
{
90-
if (Interlocked.Decrement(ref _changeNotificationsDelayed) == 0)
88+
if (Interlocked.Decrement(ref _notification.Value.ChangeNotificationsDelayed) == 0)
9189
{
92-
foreach (var distinctEvent in DistinctEvents(_propertyChangingEvents.Items.ToList()))
90+
foreach (var distinctEvent in DistinctEvents(_notification.Value.PropertyChangingEvents.Items.ToList()))
9391
{
9492
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(distinctEvent.PropertyName));
9593
}
9694

97-
foreach (var distinctEvent in DistinctEvents(_propertyChangedEvents.Items.ToList()))
95+
foreach (var distinctEvent in DistinctEvents(_notification.Value.PropertyChangedEvents.Items.ToList()))
9896
{
9997
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(distinctEvent.PropertyName));
10098
}
10199

102-
_propertyChangedEvents.Clear();
103-
_propertyChangingEvents.Clear();
100+
_notification.Value.PropertyChangingEvents.Clear();
101+
_notification.Value.PropertyChangedEvents.Clear();
104102
}
105103
});
106104
}
@@ -118,7 +116,7 @@ protected void RaisePropertyChanging(PropertyChangingEventArgs args)
118116

119117
if (AreChangeNotificationsDelayed())
120118
{
121-
_propertyChangingEvents.Add(new RxPropertyChangingEventArgs<IRxObject>(args.PropertyName, this));
119+
_notification.Value.PropertyChangingEvents.Add(new RxPropertyChangingEventArgs<IRxObject>(args.PropertyName, this));
122120
return;
123121
}
124122

@@ -148,7 +146,7 @@ protected void RaisePropertyChanged(PropertyChangedEventArgs args)
148146

149147
if (AreChangeNotificationsDelayed())
150148
{
151-
_propertyChangedEvents.Add(new RxPropertyChangedEventArgs<IRxObject>(args.PropertyName, this));
149+
_notification.Value.PropertyChangedEvents.Add(new RxPropertyChangedEventArgs<IRxObject>(args.PropertyName, this));
152150
return;
153151
}
154152

@@ -234,5 +232,14 @@ private static IEnumerable<TEventArgs> DistinctEvents<TEventArgs>(IList<TEventAr
234232
// Stack enumerates in LIFO order
235233
return uniqueEvents;
236234
}
235+
236+
[SuppressMessage("StyleCop", "SA1401", Justification = "Deliberate use of private field")]
237+
private class Notifications
238+
{
239+
public readonly SourceList<RxPropertyChangedEventArgs<IRxObject>> PropertyChangedEvents = new();
240+
public readonly SourceList<RxPropertyChangingEventArgs<IRxObject>> PropertyChangingEvents = new();
241+
public long ChangeNotificationsDelayed = 0;
242+
public long ChangeNotificationsSuppressed = 0;
243+
}
237244
}
238245
}

0 commit comments

Comments
 (0)