Fully Dynamic Query Binding with ViewModel items
using var subscription = queryController.ItemsSource
.AsChangeStream()
.WhereItems(
predicateState: queryController.NameMatchingPattern,
predicate: static (nameMatchingPattern, item) => IItem.FilterByName(item, nameMatchingPattern),
itemRefreshRequested: static item => item.WhenPropertyChangeDetected(item => item.Name))
.OrderItems(
comparisonState: queryController.OrderByColumn,
comparison: static (orderByColumn, x, y) => IItem.CompareByColumn(x, y, orderByColumn),
itemRefreshRequested: static item => item.WhenPropertyChangeDetected())
.SliceItems(queryController.PageRange)
.BindItems(bindingController.SourceItems);
Basically, I put together the most complicated scenario I could: Filter, Sort, and Page, with items that are fully mutable (meaning we need to handle single-item refreshes) and with observable state for all operators (meaning we need to handle full re-filter, re-sort, and re-page operations).
Each operator is implemented as a proof of concept, rather than optimally, using a plain collection to track upstream items, and a change-aware collection to track downstream items. E.G. the .WhereItems() operator has unfilteredItemStatesByKey and filteredItemsByKey.
Even with these naive implementations (.WhereItems() in particular is responsible for almost all of the bottlenecks, it performs very poorly with large resets), we can see drastic improvements over baseline DD.
| Method |
InitialItemCount |
MutationCount |
Mean |
Ratio |
Allocated |
Alloc Ratio |
| DynamicData |
10 |
10 |
225.98 μs |
1.00 |
190.95 KB |
1.00 |
| DynamicDataVNext |
10 |
10 |
78.81 μs |
0.35 |
96.69 KB |
0.51 |
|
|
|
|
|
|
|
| DynamicData |
10 |
100 |
1,005.35 μs |
1.00 |
827.89 KB |
1.00 |
| DynamicDataVNext |
10 |
100 |
396.90 μs |
0.39 |
437.58 KB |
0.53 |
|
|
|
|
|
|
|
| DynamicData |
10 |
1000 |
7,779.01 μs |
1.00 |
5867.75 KB |
1.00 |
| DynamicDataVNext |
10 |
1000 |
3,270.35 μs |
0.42 |
3330.13 KB |
0.57 |
|
|
|
|
|
|
|
| DynamicData |
100 |
10 |
944.48 μs |
1.00 |
931.71 KB |
1.00 |
| DynamicDataVNext |
100 |
10 |
435.14 μs |
0.46 |
658.37 KB |
0.71 |
|
|
|
|
|
|
|
| DynamicData |
100 |
100 |
1,769.13 μs |
1.00 |
1606.21 KB |
1.00 |
| DynamicDataVNext |
100 |
100 |
747.36 μs |
0.42 |
966.22 KB |
0.60 |
|
|
|
|
|
|
|
| DynamicData |
100 |
1000 |
8,293.80 μs |
1.00 |
6588.69 KB |
1.00 |
| DynamicDataVNext |
100 |
1000 |
3,844.70 μs |
0.46 |
3828.35 KB |
0.58 |
|
|
|
|
|
|
|
| DynamicData |
1000 |
10 |
6,286.88 μs |
1.00 |
4988.21 KB |
1.00 |
| DynamicDataVNext |
1000 |
10 |
14,665.62 μs |
2.33 |
20557.26 KB |
4.12 |
|
|
|
|
|
|
|
| DynamicData |
1000 |
100 |
18,004.71 μs |
1.00 |
10322.29 KB |
1.00 |
| DynamicDataVNext |
1000 |
100 |
20,800.48 μs |
1.14 |
24324.31 KB |
2.36 |
|
|
|
|
|
|
|
| DynamicData |
1000 |
1000 |
52,584.85 μs |
1.00 |
37875.97 KB |
1.00 |
| DynamicDataVNext |
1000 |
1000 |
45,030.18 μs |
0.86 |
38091.94 KB |
1.01 |
Individual Operator Profiling
For each of this, I basically took the full query above and trimmed it down to just one operator. I then calculated "Adjusted" measurements for each benchmark by subtracting out the measurement from the equivalent "InitialResetOnly" benchmark (for "IntiialResetOnly" the "SubscriptionsOnly" measurement is subtracted), as a way to roughly eliminate the overhead from setup and binding, and estimate the impact of just the operator in question.
Keyed Filtering
using var subscription = queryController.ItemsSource
.AsChangeStream()
.WhereItems(
predicateState: queryController.NameMatchingPattern,
predicate: static (nameMatchingPattern, item) => IItem.FilterByName(item, nameMatchingPattern),
itemRefreshRequested: static item => item.WhenPropertyChangeDetected(item => item.Name))
.Source
.BindItems(bindingController.SourceItems);
| Method |
Scenario |
InitialItemCount |
Mean |
Ratio |
Mean (Adjusted) |
Ratio (Adjusted) |
Allocated |
Alloc Ratio |
Allocated (Adjusted) |
Alloc Ratio (Adjusted) |
| DynamicData |
SubscriptionsOnly |
10 |
9.901 μs |
1.00 |
|
|
11.780 KB |
1.00 |
|
|
| DynamicDataVNext |
SubscriptionsOnly |
10 |
2.429 μs |
0.25 |
|
|
3.950 KB |
0.34 |
|
|
| DynamicData |
SubscriptionsOnly |
100 |
9.796 μs |
1.00 |
|
|
11.780 KB |
1.00 |
|
|
| DynamicDataVNext |
SubscriptionsOnly |
100 |
2.438 μs |
0.25 |
|
|
3.950 KB |
0.34 |
|
|
| DynamicData |
SubscriptionsOnly |
1000 |
9.822 μs |
1.00 |
|
|
11.780 KB |
1.00 |
|
|
| DynamicDataVNext |
SubscriptionsOnly |
1000 |
2.454 μs |
0.25 |
|
|
3.950 KB |
0.34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
InitialResetOnly |
10 |
58.212 μs |
1.00 |
48.311 μs |
1.00 |
51.010 KB |
1.00 |
39.230 KB |
1.00 |
| DynamicDataVNext |
InitialResetOnly |
10 |
24.243 μs |
0.42 |
21.814 μs |
0.45 |
33.910 KB |
0.66 |
29.960 KB |
0.76 |
| DynamicData |
InitialResetOnly |
100 |
388.169 μs |
1.00 |
378.373 μs |
1.00 |
400.490 KB |
1.00 |
388.710 KB |
1.00 |
| DynamicDataVNext |
InitialResetOnly |
100 |
238.705 μs |
0.61 |
236.267 μs |
0.62 |
371.480 KB |
0.93 |
367.530 KB |
0.95 |
| DynamicData |
InitialResetOnly |
1000 |
4,833.173 μs |
1.00 |
4,823.351 μs |
1.00 |
3,793.010 KB |
1.00 |
3,781.230 KB |
1.00 |
| DynamicDataVNext |
InitialResetOnly |
1000 |
6,038.374 μs |
1.25 |
6,035.920 μs |
1.25 |
10,684.370 KB |
2.82 |
10,680.420 KB |
2.82 |
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
SingleAdds |
10 |
112.677 μs |
1.00 |
54.465 μs |
1.00 |
98.550 KB |
1.00 |
47.540 KB |
1.00 |
| DynamicDataVNext |
SingleAdds |
10 |
51.288 μs |
0.46 |
27.045 μs |
0.50 |
63.520 KB |
0.64 |
29.610 KB |
0.62 |
| DynamicData |
SingleAdds |
100 |
981.782 μs |
1.00 |
593.613 μs |
1.00 |
879.240 KB |
1.00 |
478.750 KB |
1.00 |
| DynamicDataVNext |
SingleAdds |
100 |
503.478 μs |
0.51 |
264.773 μs |
0.45 |
664.800 KB |
0.76 |
293.320 KB |
0.61 |
| DynamicData |
SingleAdds |
1000 |
14,904.025 μs |
1.00 |
10,070.852 μs |
1.00 |
8,561.280 KB |
1.00 |
4,768.270 KB |
1.00 |
| DynamicDataVNext |
SingleAdds |
1000 |
11,114.191 μs |
0.75 |
5,075.817 μs |
0.50 |
13,598.740 KB |
1.59 |
2,914.370 KB |
0.61 |
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
RangeAdds |
10 |
100.604 μs |
1.00 |
42.392 μs |
1.00 |
91.870 KB |
1.00 |
40.860 KB |
1.00 |
| DynamicDataVNext |
RangeAdds |
10 |
47.279 μs |
0.47 |
23.036 μs |
0.54 |
63.140 KB |
0.69 |
29.230 KB |
0.72 |
| DynamicData |
RangeAdds |
100 |
842.374 μs |
1.00 |
454.205 μs |
1.00 |
814.420 KB |
1.00 |
413.930 KB |
1.00 |
| DynamicDataVNext |
RangeAdds |
100 |
491.308 μs |
0.58 |
252.603 μs |
0.56 |
674.630 KB |
0.83 |
303.150 KB |
0.73 |
| DynamicData |
RangeAdds |
1000 |
13,522.260 μs |
1.00 |
8,689.087 μs |
1.00 |
7,896.610 KB |
1.00 |
4,103.600 KB |
1.00 |
| DynamicDataVNext |
RangeAdds |
1000 |
10,985.739 μs |
0.81 |
4,947.365 μs |
0.57 |
13,694.000 KB |
1.73 |
3,009.630 KB |
0.73 |
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
SingleRemoves |
10 |
71.014 μs |
1.00 |
12.802 μs |
1.00 |
63.180 KB |
1.00 |
12.170 KB |
1.00 |
| DynamicDataVNext |
SingleRemoves |
10 |
26.844 μs |
0.38 |
2.601 μs |
0.20 |
35.710 KB |
0.57 |
1.800 KB |
0.15 |
| DynamicData |
SingleRemoves |
100 |
524.699 μs |
1.00 |
136.530 μs |
1.00 |
514.280 KB |
1.00 |
113.790 KB |
1.00 |
| DynamicDataVNext |
SingleRemoves |
100 |
250.397 μs |
0.48 |
11.692 μs |
0.09 |
389.450 KB |
0.76 |
17.970 KB |
0.16 |
| DynamicData |
SingleRemoves |
1000 |
6,587.480 μs |
1.00 |
1,754.307 μs |
1.00 |
5,010.490 KB |
1.00 |
1,217.480 KB |
1.00 |
| DynamicDataVNext |
SingleRemoves |
1000 |
6,376.713 μs |
0.97 |
338.339 μs |
0.19 |
10,864.120 KB |
2.17 |
179.750 KB |
0.15 |
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
RangeRemoves |
10 |
61.570 μs |
1.00 |
3.358 μs |
1.00 |
56.470 KB |
1.00 |
5.460 KB |
1.00 |
| DynamicDataVNext |
RangeRemoves |
10 |
24.395 μs |
0.40 |
0.152 μs |
0.05 |
35.340 KB |
0.63 |
1.430 KB |
0.26 |
| DynamicData |
RangeRemoves |
100 |
444.127 μs |
1.00 |
55.958 μs |
1.00 |
448.110 KB |
1.00 |
47.620 KB |
1.00 |
| DynamicDataVNext |
RangeRemoves |
100 |
230.030 μs |
0.52 |
-8.675 μs |
-0.16 |
385.770 KB |
0.86 |
14.290 KB |
0.30 |
| DynamicData |
RangeRemoves |
1000 |
5,604.619 μs |
1.00 |
771.446 μs |
1.00 |
4,340.700 KB |
1.00 |
547.690 KB |
1.00 |
| DynamicDataVNext |
RangeRemoves |
1000 |
6,186.292 μs |
1.10 |
147.918 μs |
0.19 |
10,827.400 KB |
2.49 |
143.030 KB |
0.26 |
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
ItemNameChanges |
10 |
67.622 μs |
1.00 |
9.410 μs |
1.00 |
57.530 KB |
1.00 |
6.520 KB |
1.00 |
| DynamicDataVNext |
ItemNameChanges |
10 |
27.100 μs |
0.40 |
2.857 μs |
0.30 |
33.910 KB |
0.59 |
0.000 KB |
0.00 |
| DynamicData |
ItemNameChanges |
100 |
482.901 μs |
1.00 |
94.732 μs |
1.00 |
458.540 KB |
1.00 |
58.050 KB |
1.00 |
| DynamicDataVNext |
ItemNameChanges |
100 |
257.752 μs |
0.53 |
19.047 μs |
0.20 |
371.480 KB |
0.81 |
0.000 KB |
0.00 |
| DynamicData |
ItemNameChanges |
1000 |
6,086.210 μs |
1.00 |
1,253.037 μs |
1.00 |
4,447.350 KB |
1.00 |
654.340 KB |
1.00 |
| DynamicDataVNext |
ItemNameChanges |
1000 |
6,671.290 μs |
1.10 |
632.916 μs |
0.51 |
10,684.440 KB |
2.40 |
0.070 KB |
0.00 |
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
FullReFilters |
10 |
72.234 μs |
1.00 |
14.022 μs |
1.00 |
58.560 KB |
1.00 |
7.550 KB |
1.00 |
| DynamicDataVNext |
FullReFilters |
10 |
33.676 μs |
0.47 |
9.433 μs |
0.67 |
38.260 KB |
0.65 |
4.350 KB |
0.58 |
| DynamicData |
FullReFilters |
100 |
501.256 μs |
1.00 |
113.087 μs |
1.00 |
492.010 KB |
1.00 |
91.520 KB |
1.00 |
| DynamicDataVNext |
FullReFilters |
100 |
303.154 μs |
0.60 |
64.449 μs |
0.57 |
414.370 KB |
0.84 |
42.890 KB |
0.47 |
| DynamicData |
FullReFilters |
1000 |
6,691.328 μs |
1.00 |
1,858.155 μs |
1.00 |
5,503.820 KB |
1.00 |
1,710.810 KB |
1.00 |
| DynamicDataVNext |
FullReFilters |
1000 |
7,816.093 μs |
1.17 |
1,777.719 μs |
0.96 |
11,154.740 KB |
2.03 |
470.370 KB |
0.27 |
Keyed Sorting
using var subscription = queryController.ItemsSource
.AsChangeStream()
.OrderItems(
comparisonState: queryController.OrderByColumn,
comparison: static (orderByColumn, x, y) => IItem.CompareByColumn(x, y, orderByColumn),
itemRefreshRequested: static item => item.WhenPropertyChangeDetected(),
reSortingStrategy: reSortingStrategy)
.BindItems(bindingController.SourceItems);
| Method |
Scenario |
InitialItemCount |
ReSortingStrategy |
Mean |
Ratio |
Mean (Adjusted) |
Ratio (Adjusted) |
Allocated |
Alloc Ratio |
Allocated (Adjusted) |
Alloc Ratio (Adjusted) |
| DynamicData |
SubscriptionsOnly |
10 |
|
11.575 μs |
1.00 |
|
|
12.780 KB |
1.00 |
|
|
| DynamicDataVNext |
SubscriptionsOnly |
10 |
FullReset |
2.457 μs |
0.21 |
|
|
4.030 KB |
0.32 |
|
|
| DynamicDataVNext |
SubscriptionsOnly |
10 |
OptimalSwaps |
2.371 μs |
0.20 |
|
|
4.030 KB |
0.32 |
|
|
| DynamicData |
SubscriptionsOnly |
100 |
|
11.554 μs |
1.00 |
|
|
12.780 KB |
1.00 |
|
|
| DynamicDataVNext |
SubscriptionsOnly |
100 |
FullReset |
2.428 μs |
0.21 |
|
|
4.030 KB |
0.32 |
|
|
| DynamicDataVNext |
SubscriptionsOnly |
100 |
OptimalSwaps |
2.401 μs |
0.21 |
|
|
4.030 KB |
0.32 |
|
|
| DynamicData |
SubscriptionsOnly |
1000 |
|
11.421 μs |
1.00 |
|
|
12.780 KB |
1.00 |
|
|
| DynamicDataVNext |
SubscriptionsOnly |
1000 |
FullReset |
2.535 μs |
0.22 |
|
|
4.030 KB |
0.32 |
|
|
| DynamicDataVNext |
SubscriptionsOnly |
1000 |
OptimalSwaps |
2.400 μs |
0.21 |
|
|
4.030 KB |
0.32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
InitialResetOnly |
10 |
|
62.175 μs |
1.00 |
50.600 μs |
1.00 |
54.130 KB |
1.00 |
41.350 KB |
1.00 |
| DynamicDataVNext |
InitialResetOnly |
10 |
FullReset |
19.890 μs |
0.32 |
17.433 μs |
0.34 |
26.150 KB |
0.48 |
22.120 KB |
0.53 |
| DynamicDataVNext |
InitialResetOnly |
10 |
OptimalSwaps |
18.517 μs |
0.30 |
16.146 μs |
0.32 |
26.150 KB |
0.48 |
22.120 KB |
0.53 |
| DynamicData |
InitialResetOnly |
100 |
|
384.950 μs |
1.00 |
373.396 μs |
1.00 |
408.510 KB |
1.00 |
395.730 KB |
1.00 |
| DynamicDataVNext |
InitialResetOnly |
100 |
FullReset |
158.970 μs |
0.41 |
156.542 μs |
0.42 |
290.030 KB |
0.71 |
286.000 KB |
0.72 |
| DynamicDataVNext |
InitialResetOnly |
100 |
OptimalSwaps |
157.712 μs |
0.41 |
155.311 μs |
0.42 |
290.030 KB |
0.71 |
286.000 KB |
0.72 |
| DynamicData |
InitialResetOnly |
1000 |
|
4,758.403 μs |
1.00 |
4,746.982 μs |
1.00 |
3,819.320 KB |
1.00 |
3,806.540 KB |
1.00 |
| DynamicDataVNext |
InitialResetOnly |
1000 |
FullReset |
5,029.370 μs |
1.06 |
5,026.835 μs |
1.06 |
9,879.820 KB |
2.59 |
9,875.790 KB |
2.59 |
| DynamicDataVNext |
InitialResetOnly |
1000 |
OptimalSwaps |
5,038.079 μs |
1.06 |
5,035.679 μs |
1.06 |
9,879.920 KB |
2.59 |
9,875.890 KB |
2.59 |
|
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
SingleAdds |
10 |
|
126.527 μs |
1.00 |
64.352 μs |
1.00 |
109.610 KB |
1.00 |
55.480 KB |
1.00 |
| DynamicDataVNext |
SingleAdds |
10 |
FullReset |
35.401 μs |
0.28 |
15.511 μs |
0.24 |
47.670 KB |
0.43 |
21.520 KB |
0.39 |
| DynamicDataVNext |
SingleAdds |
10 |
OptimalSwaps |
35.429 μs |
0.28 |
16.912 μs |
0.26 |
47.670 KB |
0.43 |
21.520 KB |
0.39 |
| DynamicData |
SingleAdds |
100 |
|
1,078.748 μs |
1.00 |
693.798 μs |
1.00 |
1,178.210 KB |
1.00 |
769.700 KB |
1.00 |
| DynamicDataVNext |
SingleAdds |
100 |
FullReset |
360.018 μs |
0.33 |
201.048 μs |
0.29 |
502.570 KB |
0.43 |
212.540 KB |
0.28 |
| DynamicDataVNext |
SingleAdds |
100 |
OptimalSwaps |
349.438 μs |
0.32 |
191.726 μs |
0.28 |
502.570 KB |
0.43 |
212.540 KB |
0.28 |
| DynamicData |
SingleAdds |
1000 |
|
29,627.040 μs |
1.00 |
24,868.637 μs |
1.00 |
32,568.980 KB |
1.00 |
28,749.660 KB |
1.00 |
| DynamicDataVNext |
SingleAdds |
1000 |
FullReset |
8,781.762 μs |
0.30 |
3,752.392 μs |
0.15 |
11,988.010 KB |
0.37 |
2,108.190 KB |
0.07 |
| DynamicDataVNext |
SingleAdds |
1000 |
OptimalSwaps |
8,828.382 μs |
0.30 |
3,790.303 μs |
0.15 |
11,987.980 KB |
0.37 |
2,108.060 KB |
0.07 |
|
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
RangeAdds |
10 |
|
107.268 μs |
1.00 |
45.093 μs |
1.00 |
95.840 KB |
1.00 |
41.710 KB |
1.00 |
| DynamicDataVNext |
RangeAdds |
10 |
FullReset |
33.597 μs |
0.31 |
13.707 μs |
0.30 |
47.300 KB |
0.49 |
21.150 KB |
0.51 |
| DynamicDataVNext |
RangeAdds |
10 |
OptimalSwaps |
33.233 μs |
0.31 |
14.716 μs |
0.33 |
47.300 KB |
0.49 |
21.150 KB |
0.51 |
| DynamicData |
RangeAdds |
100 |
|
879.359 μs |
1.00 |
494.409 μs |
1.00 |
845.610 KB |
1.00 |
437.100 KB |
1.00 |
| DynamicDataVNext |
RangeAdds |
100 |
FullReset |
339.431 μs |
0.39 |
180.461 μs |
0.37 |
512.400 KB |
0.61 |
222.370 KB |
0.51 |
| DynamicDataVNext |
RangeAdds |
100 |
OptimalSwaps |
338.847 μs |
0.39 |
181.135 μs |
0.37 |
512.400 KB |
0.61 |
222.370 KB |
0.51 |
| DynamicData |
RangeAdds |
1000 |
|
15,633.389 μs |
1.00 |
10,874.986 μs |
1.00 |
10,300.710 KB |
1.00 |
6,481.390 KB |
1.00 |
| DynamicDataVNext |
RangeAdds |
1000 |
FullReset |
9,001.438 μs |
0.58 |
3,972.068 μs |
0.37 |
12,083.450 KB |
1.17 |
2,203.630 KB |
0.34 |
| DynamicDataVNext |
RangeAdds |
1000 |
OptimalSwaps |
8,432.241 μs |
0.54 |
8,370.066 μs |
0.77 |
12,083.370 KB |
1.17 |
12,029.240 KB |
1.86 |
|
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
SingleRemoves |
10 |
|
96.259 μs |
1.00 |
34.084 μs |
1.00 |
77.550 KB |
1.00 |
23.420 KB |
1.00 |
| DynamicDataVNext |
SingleRemoves |
10 |
FullReset |
19.197 μs |
0.20 |
-0.693 μs |
-0.02 |
27.950 KB |
0.36 |
1.800 KB |
0.08 |
| DynamicDataVNext |
SingleRemoves |
10 |
OptimalSwaps |
19.146 μs |
0.20 |
0.629 μs |
0.02 |
27.950 KB |
0.36 |
1.800 KB |
0.08 |
| DynamicData |
SingleRemoves |
100 |
|
1,070.065 μs |
1.00 |
685.115 μs |
1.00 |
1,068.790 KB |
1.00 |
660.280 KB |
1.00 |
| DynamicDataVNext |
SingleRemoves |
100 |
FullReset |
179.375 μs |
0.17 |
20.405 μs |
0.03 |
308.000 KB |
0.29 |
17.970 KB |
0.03 |
| DynamicDataVNext |
SingleRemoves |
100 |
OptimalSwaps |
179.875 μs |
0.17 |
22.163 μs |
0.03 |
308.000 KB |
0.29 |
17.970 KB |
0.03 |
| DynamicData |
SingleRemoves |
1000 |
|
51,815.737 μs |
1.00 |
47,057.334 μs |
1.00 |
51,138.210 KB |
1.00 |
47,318.890 KB |
1.00 |
| DynamicDataVNext |
SingleRemoves |
1000 |
FullReset |
5,487.537 μs |
0.11 |
458.167 μs |
0.01 |
10,059.580 KB |
0.20 |
179.760 KB |
0.00 |
| DynamicDataVNext |
SingleRemoves |
1000 |
OptimalSwaps |
5,495.602 μs |
0.11 |
457.523 μs |
0.01 |
10,059.500 KB |
0.20 |
179.580 KB |
0.00 |
|
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
RangeRemoves |
10 |
|
73.968 μs |
1.00 |
11.793 μs |
1.00 |
64.880 KB |
1.00 |
10.750 KB |
1.00 |
| DynamicDataVNext |
RangeRemoves |
10 |
FullReset |
17.998 μs |
0.24 |
-1.892 μs |
-0.16 |
27.580 KB |
0.43 |
1.430 KB |
0.13 |
| DynamicDataVNext |
RangeRemoves |
10 |
OptimalSwaps |
17.492 μs |
0.24 |
-1.025 μs |
-0.09 |
27.580 KB |
0.43 |
1.430 KB |
0.13 |
| DynamicData |
RangeRemoves |
100 |
|
900.686 μs |
1.00 |
515.736 μs |
1.00 |
877.980 KB |
1.00 |
469.470 KB |
1.00 |
| DynamicDataVNext |
RangeRemoves |
100 |
FullReset |
164.791 μs |
0.18 |
5.821 μs |
0.01 |
304.330 KB |
0.35 |
14.300 KB |
0.03 |
| DynamicDataVNext |
RangeRemoves |
100 |
OptimalSwaps |
164.605 μs |
0.18 |
6.893 μs |
0.01 |
304.330 KB |
0.35 |
14.300 KB |
0.03 |
| DynamicData |
RangeRemoves |
1000 |
|
48,566.171 μs |
1.00 |
43,807.768 μs |
1.00 |
42,119.370 KB |
1.00 |
38,300.050 KB |
1.00 |
| DynamicDataVNext |
RangeRemoves |
1000 |
FullReset |
5,325.806 μs |
0.11 |
296.436 μs |
0.01 |
10,022.840 KB |
0.24 |
143.020 KB |
0.00 |
| DynamicDataVNext |
RangeRemoves |
1000 |
OptimalSwaps |
5,187.748 μs |
0.11 |
149.669 μs |
0.00 |
10,022.750 KB |
0.24 |
142.830 KB |
0.00 |
|
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
FullReSorts |
10 |
|
110.654 μs |
1.00 |
48.479 μs |
1.00 |
90.160 KB |
1.00 |
36.030 KB |
1.00 |
| DynamicDataVNext |
FullReSorts |
10 |
FullReset |
29.370 μs |
0.27 |
9.480 μs |
0.20 |
44.630 KB |
0.50 |
18.480 KB |
0.51 |
| DynamicDataVNext |
FullReSorts |
10 |
OptimalSwaps |
25.845 μs |
0.23 |
7.328 μs |
0.15 |
30.000 KB |
0.33 |
3.850 KB |
0.11 |
| DynamicData |
FullReSorts |
100 |
|
541.934 μs |
1.00 |
156.984 μs |
1.00 |
569.600 KB |
1.00 |
161.090 KB |
1.00 |
| DynamicDataVNext |
FullReSorts |
100 |
FullReset |
293.293 μs |
0.54 |
134.323 μs |
0.86 |
472.340 KB |
0.83 |
182.310 KB |
1.13 |
| DynamicDataVNext |
FullReSorts |
100 |
OptimalSwaps |
544.377 μs |
1.00 |
386.665 μs |
2.46 |
338.920 KB |
0.60 |
48.890 KB |
0.30 |
| DynamicData |
FullReSorts |
1000 |
|
7,457.712 μs |
1.00 |
2,699.309 μs |
1.00 |
5,462.260 KB |
1.00 |
1,642.940 KB |
1.00 |
| DynamicDataVNext |
FullReSorts |
1000 |
FullReset |
7,687.292 μs |
1.03 |
2,657.922 μs |
0.98 |
11,700.560 KB |
2.14 |
1,820.740 KB |
1.11 |
| DynamicDataVNext |
FullReSorts |
1000 |
OptimalSwaps |
37,832.752 μs |
5.07 |
32,794.673 μs |
12.15 |
10,391.960 KB |
1.90 |
512.040 KB |
0.31 |
Sorted Paging
using var subscription = queryController.ItemsSource
.SliceItems(queryController.PageRange)
.BindItems(bindingController.SourceItems);
| Method |
Scenario |
InitialItemCount |
Mean |
Ratio |
Mean (Adjusted) |
Ratio (Adjusted) |
Allocated |
Alloc Ratio |
Allocated (Adjusted) |
Alloc Ratio (Adjusted) |
| DynamicData |
SubscriptionsOnly |
10 |
13.103 μs |
1.00 |
|
|
12.270 KB |
1.00 |
|
|
| DynamicDataVNext |
SubscriptionsOnly |
10 |
2.495 μs |
0.19 |
|
|
3.670 KB |
0.30 |
|
|
| DynamicData |
SubscriptionsOnly |
100 |
12.847 μs |
1.00 |
|
|
12.270 KB |
1.00 |
|
|
| DynamicDataVNext |
SubscriptionsOnly |
100 |
2.468 μs |
0.19 |
|
|
3.670 KB |
0.30 |
|
|
| DynamicData |
SubscriptionsOnly |
1000 |
12.853 μs |
1.00 |
|
|
12.270 KB |
1.00 |
|
|
| DynamicDataVNext |
SubscriptionsOnly |
1000 |
2.551 μs |
0.20 |
|
|
3.670 KB |
0.30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
InitialResetOnly |
10 |
66.599 μs |
1.00 |
53.496 μs |
1.00 |
48.770 KB |
1.00 |
36.500 KB |
1.00 |
| DynamicDataVNext |
InitialResetOnly |
10 |
8.614 μs |
0.13 |
6.119 μs |
0.11 |
10.410 KB |
0.21 |
6.740 KB |
0.18 |
| DynamicData |
InitialResetOnly |
100 |
418.831 μs |
1.00 |
405.984 μs |
1.00 |
342.520 KB |
1.00 |
330.250 KB |
1.00 |
| DynamicDataVNext |
InitialResetOnly |
100 |
53.702 μs |
0.13 |
51.234 μs |
0.13 |
69.340 KB |
0.20 |
65.670 KB |
0.20 |
| DynamicData |
InitialResetOnly |
1000 |
5,161.761 μs |
1.00 |
5,148.908 μs |
1.00 |
3,201.010 KB |
1.00 |
3,188.740 KB |
1.00 |
| DynamicDataVNext |
InitialResetOnly |
1000 |
581.715 μs |
0.11 |
579.164 μs |
0.11 |
631.700 KB |
0.20 |
628.030 KB |
0.20 |
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
SingleAdds |
10 |
132.680 μs |
1.00 |
66.081 μs |
1.00 |
97.880 KB |
1.00 |
49.110 KB |
1.00 |
| DynamicDataVNext |
SingleAdds |
10 |
16.921 μs |
0.13 |
8.307 μs |
0.13 |
16.840 KB |
0.17 |
6.430 KB |
0.13 |
| DynamicData |
SingleAdds |
100 |
1,213.345 μs |
1.00 |
794.514 μs |
1.00 |
828.320 KB |
1.00 |
485.800 KB |
1.00 |
| DynamicDataVNext |
SingleAdds |
100 |
125.578 μs |
0.10 |
71.876 μs |
0.09 |
130.950 KB |
0.16 |
61.610 KB |
0.13 |
| DynamicData |
SingleAdds |
1000 |
27,320.463 μs |
1.00 |
22,158.702 μs |
1.00 |
8,052.600 KB |
1.00 |
4,851.590 KB |
1.00 |
| DynamicDataVNext |
SingleAdds |
1000 |
1,482.437 μs |
0.05 |
900.722 μs |
0.04 |
1,234.560 KB |
0.15 |
602.860 KB |
0.12 |
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
RangeAdds |
10 |
106.656 μs |
1.00 |
40.057 μs |
1.00 |
87.090 KB |
1.00 |
38.320 KB |
1.00 |
| DynamicDataVNext |
RangeAdds |
10 |
13.459 μs |
0.13 |
4.845 μs |
0.12 |
16.470 KB |
0.19 |
6.060 KB |
0.16 |
| DynamicData |
RangeAdds |
100 |
956.288 μs |
1.00 |
537.457 μs |
1.00 |
702.640 KB |
1.00 |
360.120 KB |
1.00 |
| DynamicDataVNext |
RangeAdds |
100 |
103.506 μs |
0.11 |
49.804 μs |
0.09 |
127.200 KB |
0.18 |
57.860 KB |
0.16 |
| DynamicData |
RangeAdds |
1000 |
20,930.170 μs |
1.00 |
15,768.409 μs |
1.00 |
6,802.040 KB |
1.00 |
3,601.030 KB |
1.00 |
| DynamicDataVNext |
RangeAdds |
1000 |
1,311.185 μs |
0.06 |
729.470 μs |
0.05 |
1,197.030 KB |
0.18 |
565.330 KB |
0.16 |
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
SingleRemoves |
10 |
84.458 μs |
1.00 |
17.859 μs |
1.00 |
68.370 KB |
1.00 |
19.600 KB |
1.00 |
| DynamicDataVNext |
SingleRemoves |
10 |
9.709 μs |
0.11 |
1.095 μs |
0.06 |
12.080 KB |
0.18 |
1.670 KB |
0.09 |
| DynamicData |
SingleRemoves |
100 |
621.418 μs |
1.00 |
202.587 μs |
1.00 |
525.780 KB |
1.00 |
183.260 KB |
1.00 |
| DynamicDataVNext |
SingleRemoves |
100 |
72.569 μs |
0.12 |
18.867 μs |
0.09 |
86.560 KB |
0.16 |
17.220 KB |
0.09 |
| DynamicData |
SingleRemoves |
1000 |
7,358.187 μs |
1.00 |
2,196.426 μs |
1.00 |
5,090.690 KB |
1.00 |
1,889.680 KB |
1.00 |
| DynamicDataVNext |
SingleRemoves |
1000 |
1,002.443 μs |
0.14 |
420.728 μs |
0.19 |
810.380 KB |
0.16 |
178.680 KB |
0.09 |
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
RangeRemoves |
10 |
70.405 μs |
1.00 |
3.806 μs |
1.00 |
54.790 KB |
1.00 |
6.020 KB |
1.00 |
| DynamicDataVNext |
RangeRemoves |
10 |
8.495 μs |
0.12 |
-0.119 μs |
-0.03 |
10.740 KB |
0.20 |
0.330 KB |
0.05 |
| DynamicData |
RangeRemoves |
100 |
533.530 μs |
1.00 |
114.699 μs |
1.00 |
491.510 KB |
1.00 |
148.990 KB |
1.00 |
| DynamicDataVNext |
RangeRemoves |
100 |
51.877 μs |
0.10 |
-1.825 μs |
-0.02 |
82.050 KB |
0.17 |
12.710 KB |
0.09 |
| DynamicData |
RangeRemoves |
1000 |
14,653.399 μs |
1.00 |
9,491.638 μs |
1.00 |
11,350.390 KB |
1.00 |
8,149.380 KB |
1.00 |
| DynamicDataVNext |
RangeRemoves |
1000 |
740.295 μs |
0.05 |
158.580 μs |
0.02 |
768.150 KB |
0.07 |
136.450 KB |
0.02 |
|
|
|
|
|
|
|
|
|
|
|
| DynamicData |
FullReSlices |
10 |
64.375 μs |
1.00 |
-2.224 μs |
1.00 |
48.750 KB |
1.00 |
-0.020 KB |
1.00 |
| DynamicDataVNext |
FullReSlices |
10 |
14.439 μs |
0.22 |
5.825 μs |
-2.62 |
18.300 KB |
0.38 |
7.890 KB |
-394.50 |
| DynamicData |
FullReSlices |
100 |
454.230 μs |
1.00 |
35.399 μs |
1.00 |
342.560 KB |
1.00 |
0.040 KB |
1.00 |
| DynamicDataVNext |
FullReSlices |
100 |
83.941 μs |
0.18 |
30.239 μs |
0.85 |
118.410 KB |
0.35 |
49.070 KB |
1226.75 |
| DynamicData |
FullReSlices |
1000 |
5,380.040 μs |
1.00 |
218.279 μs |
1.00 |
3,200.680 KB |
1.00 |
-0.330 KB |
1.00 |
| DynamicDataVNext |
FullReSlices |
1000 |
975.139 μs |
0.18 |
393.424 μs |
1.80 |
1,116.700 KB |
0.35 |
485.000 KB |
-1469.70 |
Fully Dynamic Query Binding with ViewModel items
Basically, I put together the most complicated scenario I could: Filter, Sort, and Page, with items that are fully mutable (meaning we need to handle single-item refreshes) and with observable state for all operators (meaning we need to handle full re-filter, re-sort, and re-page operations).
Each operator is implemented as a proof of concept, rather than optimally, using a plain collection to track upstream items, and a change-aware collection to track downstream items. E.G. the
.WhereItems()operator hasunfilteredItemStatesByKeyandfilteredItemsByKey.Even with these naive implementations (
.WhereItems()in particular is responsible for almost all of the bottlenecks, it performs very poorly with large resets), we can see drastic improvements over baseline DD.Individual Operator Profiling
For each of this, I basically took the full query above and trimmed it down to just one operator. I then calculated "Adjusted" measurements for each benchmark by subtracting out the measurement from the equivalent "InitialResetOnly" benchmark (for "IntiialResetOnly" the "SubscriptionsOnly" measurement is subtracted), as a way to roughly eliminate the overhead from setup and binding, and estimate the impact of just the operator in question.
Keyed Filtering
Keyed Sorting
Sorted Paging