Skip to content

Commit

Permalink
Simplify ShapeDescriptorIndex (#15632)
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeAlhayek authored Apr 3, 2024
1 parent aa0b8c3 commit 74d923c
Show file tree
Hide file tree
Showing 3 changed files with 370 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Html;
using OrchardCore.DisplayManagement.Implementation;
Expand All @@ -22,63 +21,47 @@ public FeatureShapeDescriptor(IFeatureInfo feature, string shapeType)

public class ShapeDescriptorIndex : ShapeDescriptor
{
private readonly ConcurrentDictionary<string, FeatureShapeDescriptor> _descriptors;
private readonly List<FeatureShapeDescriptor> _alternationDescriptors;
private readonly List<string> _wrappers;
private readonly List<string> _bindingSources;
private readonly Dictionary<string, ShapeBinding> _bindings;
private readonly List<Func<ShapeCreatingContext, Task>> _creatingAsync;
private readonly List<Func<ShapeCreatedContext, Task>> _createdAsync;
private readonly List<Func<ShapeDisplayContext, Task>> _displayingAsync;
private readonly List<Func<ShapeDisplayContext, Task>> _processingAsync;
private readonly List<Func<ShapeDisplayContext, Task>> _displayedAsync;
private readonly List<FeatureShapeDescriptor> _alternationDescriptors = [];
private readonly List<string> _wrappers = [];
private readonly List<string> _bindingSources = [];
private readonly Dictionary<string, ShapeBinding> _bindings = new(StringComparer.OrdinalIgnoreCase);
private readonly List<Func<ShapeCreatingContext, Task>> _creatingAsync = [];
private readonly List<Func<ShapeCreatedContext, Task>> _createdAsync = [];
private readonly List<Func<ShapeDisplayContext, Task>> _displayingAsync = [];
private readonly List<Func<ShapeDisplayContext, Task>> _processingAsync = [];
private readonly List<Func<ShapeDisplayContext, Task>> _displayedAsync = [];

public ShapeDescriptorIndex(
string shapeType,
IEnumerable<string> alterationKeys,
ConcurrentDictionary<string, FeatureShapeDescriptor> descriptors)
{
ArgumentException.ThrowIfNullOrEmpty(shapeType);

ShapeType = shapeType;
_descriptors = descriptors;

// pre-calculate as much as we can
_alternationDescriptors = alterationKeys
.Select(key => _descriptors[key])
.ToList();

_wrappers = _alternationDescriptors
.SelectMany(sd => sd.Wrappers)
.ToList();

_bindingSources = _alternationDescriptors
.SelectMany(sd => sd.BindingSources)
.ToList();

_bindings = _alternationDescriptors
.SelectMany(sd => sd.Bindings)
.GroupBy(kv => kv.Key, StringComparer.OrdinalIgnoreCase)
.Select(kv => kv.Last())
.ToDictionary(kv => kv.Key, kv => kv.Value, StringComparer.OrdinalIgnoreCase);

_creatingAsync = _alternationDescriptors
.SelectMany(sd => sd.CreatingAsync)
.ToList();

_createdAsync = _alternationDescriptors
.SelectMany(sd => sd.CreatedAsync)
.ToList();

_displayingAsync = _alternationDescriptors
.SelectMany(sd => sd.DisplayingAsync)
.ToList();

_processingAsync = _alternationDescriptors
.SelectMany(sd => sd.ProcessingAsync)
.ToList();

_displayedAsync = _alternationDescriptors
.SelectMany(sd => sd.DisplayedAsync)
.ToList();

// Pre-calculate as much as we can for performance reasons.
foreach (var alterationKey in alterationKeys)
{
if (!descriptors.TryGetValue(alterationKey, out var alternationDescriptor))
{
continue;
}

_alternationDescriptors.Add(alternationDescriptor);
_wrappers.AddRange(alternationDescriptor.Wrappers);
_bindingSources.AddRange(alternationDescriptor.BindingSources);
_creatingAsync.AddRange(alternationDescriptor.CreatingAsync);
_createdAsync.AddRange(alternationDescriptor.CreatedAsync);
_displayingAsync.AddRange(alternationDescriptor.DisplayingAsync);
_displayedAsync.AddRange(alternationDescriptor.DisplayedAsync);
_processingAsync.AddRange(alternationDescriptor.ProcessingAsync);

foreach (var binding in alternationDescriptor.Bindings)
{
_bindings[binding.Key] = binding.Value;
}
}
}

/// <summary>
Expand Down
110 changes: 110 additions & 0 deletions test/OrchardCore.Benchmarks/ShapeDescriptorIndexBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.DependencyInjection;
using OrchardCore.Benchmark.Support;
using OrchardCore.DisplayManagement.Descriptors;
using OrchardCore.Environment.Extensions;
using OrchardCore.Tests.Apis.Context;

namespace OrchardCore.Benchmark;

[MemoryDiagnoser]
public class ShapeDescriptorIndexBenchmark
{
private readonly ConcurrentDictionary<string, FeatureShapeDescriptor> _shapeDescriptors = new();

[GlobalSetup]
public async Task SetupAsync()
{
using var content = new BlogContext();
await content.InitializeAsync();
await content.UsingTenantScopeAsync(async scope =>
{
var bindingStrategies = scope.ServiceProvider.GetRequiredService<IEnumerable<IShapeTableProvider>>();
var typeFeatureProvider = scope.ServiceProvider.GetRequiredService<ITypeFeatureProvider>();
foreach (var bindingStrategy in bindingStrategies)
{
var strategyFeature = typeFeatureProvider.GetFeatureForDependency(bindingStrategy.GetType());
var builder = new ShapeTableBuilder(strategyFeature, []);
await bindingStrategy.DiscoverAsync(builder);
BuildDescriptors(bindingStrategy, builder.BuildAlterations());
}
});
}

[Benchmark(Baseline = true)]
public List<ShapeDescriptorIndex> SingleLoopLists()
{
return _shapeDescriptors
.GroupBy(sd => sd.Value.ShapeType, StringComparer.OrdinalIgnoreCase)
.Select(group => new ShapeDescriptorIndex
(
shapeType: group.Key,
alterationKeys: group.Select(kv => kv.Key),
descriptors: _shapeDescriptors
))
.ToList();
}

[Benchmark]
public List<MultiSelectShapeDescriptorIndex> MultipleLoopsUsingLists()
{
return _shapeDescriptors
.GroupBy(sd => sd.Value.ShapeType, StringComparer.OrdinalIgnoreCase)
.Select(group => new MultiSelectShapeDescriptorIndex
(
shapeType: group.Key,
alterationKeys: group.Select(kv => kv.Key),
descriptors: _shapeDescriptors
))
.ToList();
}

[Benchmark]
public List<MultiSelectShapeDescriptorIndexArray> MultipleLoopsArrays()
{
return _shapeDescriptors
.GroupBy(sd => sd.Value.ShapeType, StringComparer.OrdinalIgnoreCase)
.Select(group => new MultiSelectShapeDescriptorIndexArray
(
shapeType: group.Key,
alterationKeys: group.Select(kv => kv.Key),
descriptors: _shapeDescriptors
))
.ToList();
}

private void BuildDescriptors(IShapeTableProvider bindingStrategy, IEnumerable<ShapeAlteration> builtAlterations)
{
var alterationSets = builtAlterations.GroupBy(a => a.Feature.Id + a.ShapeType);

foreach (var alterations in alterationSets)
{
var firstAlteration = alterations.First();

var key = bindingStrategy.GetType().Name
+ firstAlteration.Feature.Id
+ firstAlteration.ShapeType.ToLower();

if (!_shapeDescriptors.ContainsKey(key))
{
var descriptor = new FeatureShapeDescriptor
(
firstAlteration.Feature,
firstAlteration.ShapeType
);

foreach (var alteration in alterations)
{
alteration.Alter(descriptor);
}

_shapeDescriptors[key] = descriptor;
}
}
}
}
Loading

0 comments on commit 74d923c

Please sign in to comment.