Skip to content

Commit

Permalink
Improved accumulator performance
Browse files Browse the repository at this point in the history
  • Loading branch information
NikolayPianikov committed Apr 11, 2024
1 parent 5884595 commit 5ec3b14
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 53 deletions.
42 changes: 42 additions & 0 deletions benchmarks/Pure.DI.Benchmarks/OwnedBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// ReSharper disable InconsistentlySynchronizedField
namespace Pure.DI.Benchmarks;

using System.Diagnostics.CodeAnalysis;
using BenchmarkDotNet.Attributes;
using Moq;

[SuppressMessage("Performance", "CA1822:Пометьте члены как статические")]
public class OwnedBenchmark
{
private const int Count = 128;
private static readonly IDisposable Disposable = Mock.Of<IDisposable>();

[Benchmark(Baseline = true)]
public List<IDisposable> ListAdd()
{
var list = new List<IDisposable>();
for (var i = 0; i < Count; i++)
{
if (Disposable is IOwned)
{
continue;
}

list.Add(Disposable);
}

return list;
}

[Benchmark]
public IOwned OwnedAdd()
{
var owned = new Owned();
for (var i = 0; i < Count; i++)
{
owned.Add(Disposable);
}

return owned;
}
}
1 change: 1 addition & 0 deletions benchmarks/Pure.DI.Benchmarks/Pure.DI.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.12" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="IoC.Container" Version="1.3.8" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="SimpleInjector" Version="5.4.4" />
<PackageReference Include="Unity" Version="5.11.10" />
<PackageReference Include="DryIoc.dll" Version="5.4.3" />
Expand Down
53 changes: 8 additions & 45 deletions src/Pure.DI.Core/Components/Api.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1102,57 +1102,26 @@ public interface IOwned : global::System.IDisposable
[global::System.Diagnostics.DebuggerTypeProxy(typeof(global::Pure.DI.Owned.DebugView))]
internal partial class Owned : global::Pure.DI.IOwned
{
private const int InitialSize = 8;
private int _count;
private global::System.IDisposable[] _disposables = new global::System.IDisposable[InitialSize];

private global::System.Collections.Generic.List<IDisposable> _disposables = new global::System.Collections.Generic.List<IDisposable>();

/// <summary>
/// Adds a disposable instance.
/// </summary>
/// <param name="disposable">The disposable instance.</param>
[global::System.Runtime.CompilerServices.MethodImpl((global::System.Runtime.CompilerServices.MethodImplOptions)256)]
public void Add(global::System.IDisposable disposable)
{
if (disposable is global::Pure.DI.IOwned)
if (!(disposable is global::Pure.DI.IOwned))
{
return;
}

lock (_disposables)
{
var size = _disposables.Length;
if (size == _count)
{
var disposables = new global::System.IDisposable[size << 1];
global::System.Array.Copy(_disposables, disposables, size);
_disposables = disposables;
}

_disposables[_count++] = disposable;
_disposables.Add(disposable);
}
}

/// <inheritdoc />
[global::System.Runtime.CompilerServices.MethodImpl((global::System.Runtime.CompilerServices.MethodImplOptions)512)]
public void Dispose()
{
if (_disposables == null)
{
return;
}

int count;
global::System.IDisposable[] disposables;
lock (_disposables)
{
count = _count;
disposables = new global::System.IDisposable[count];
global::System.Array.Copy(_disposables, disposables, count);
_count = 0;
_disposables = new global::System.IDisposable[InitialSize];
}

for (int i = count - 1; i >= 0; i--)
var disposables = global::System.Threading.Interlocked.Exchange(ref _disposables, new global::System.Collections.Generic.List<IDisposable>());
for (int i = disposables.Count - 1; i >= 0; i--)
{
var disposable = disposables[i];
try
Expand Down Expand Up @@ -1189,13 +1158,7 @@ public DebugView(global::Pure.DI.Owned owned)
{
get
{
global::System.Collections.Generic.List<global::System.IDisposable> list = new global::System.Collections.Generic.List<global::System.IDisposable>(_owned._count);
for (int i = 0; i < _owned._count; i++)
{
list.Add(_owned._disposables[i]);
}

return list;
return _owned._disposables;
}
}
}
Expand Down
30 changes: 26 additions & 4 deletions src/Pure.DI.Core/Core/Code/BuildTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,35 @@ public IEnumerable<Line> OnCreated(BuildContext ctx, Variable variable)
.Concat(Enumerable.Repeat(variable.InstanceType, 1))
.ToImmutableHashSet(SymbolEqualityComparer.Default);

var lines = ctx.Accumulators
var code = new LinesBuilder();
var lockIsRequired = ctx.LockIsRequired ?? ctx.DependencyGraph.Source.Hints.IsThreadSafeEnabled;
var accLines = ctx.Accumulators
.Where(i => FilterAccumulator(i, variable.Node.Lifetime))
.Where(i => baseTypes.Contains(i.Type))
.GroupBy(i => i.Name)
.Select(i => i.First())
.OrderBy(i => i.Name)
.Select(i => new Line(0, $"{i.Name}.Add({variable.VariableName});"))
.ToList();

if (lockIsRequired && accLines.Count > 0)
{
code.AppendLine($"lock ({Names.LockFieldName})");
code.AppendLine("{");
code.IncIndent();
}

code.AppendLines(accLines);

if (lockIsRequired && accLines.Count > 0)
{
code.DecIndent();
code.AppendLine("}");
}

if (!ctx.DependencyGraph.Source.Hints.IsOnNewInstanceEnabled)
{
return lines;
return code.Lines;
}

if (!filter.IsMeetRegularExpression(
Expand All @@ -71,11 +88,16 @@ public IEnumerable<Line> OnCreated(BuildContext ctx, Variable variable)
(Hint.OnNewInstanceTagRegularExpression, variable.Injection.Tag.ValueToString()),
(Hint.OnNewInstanceLifetimeRegularExpression, variable.Node.Lifetime.ValueToString())))
{
return lines;
return code.Lines;
}

var tag = GetTag(ctx, variable);
lines.Insert(0, new Line(0, $"{Names.OnNewInstanceMethodName}<{typeResolver.Resolve(variable.InstanceType)}>(ref {variable.VariableName}, {tag.ValueToString()}, {variable.Node.Lifetime.ValueToString()});"));
var lines = new List<Line>
{
new(0, $"{Names.OnNewInstanceMethodName}<{typeResolver.Resolve(variable.InstanceType)}>(ref {variable.VariableName}, {tag.ValueToString()}, {variable.Node.Lifetime.ValueToString()});")
};

lines.AddRange(code.Lines);
return lines;
}

Expand Down
4 changes: 3 additions & 1 deletion src/Pure.DI.Core/Core/Code/CompositionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public CompositionCode Build(DependencyGraph graph)
var map = new VariablesMap();
var allArgs = new HashSet<Variable>();
var isThreadSafe = false;
var isThreadSafeEnabled = graph.Source.Hints.IsThreadSafeEnabled;
foreach (var root in graph.Roots.Values)
{
cancellationToken.ThrowIfCancellationRequested();
Expand Down Expand Up @@ -71,7 +72,8 @@ public CompositionCode Build(DependencyGraph graph)
};

roots.Add(processedRoot);
isThreadSafe |= map.IsThreadSafe(graph.Source.Hints);
isThreadSafe |= isThreadSafeEnabled && root.Node.Accumulators.Count > 0;
isThreadSafe |= isThreadSafeEnabled && map.IsThreadSafe(graph.Source.Hints);
map.Reset();
}

Expand Down
3 changes: 1 addition & 2 deletions src/Pure.DI.Core/Core/Code/VariablesMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ public IEnumerable<Variable> GetSingletons() => this
.Select(i => i.Value);

public bool IsThreadSafe(IHints hints) =>
hints.IsThreadSafeEnabled
&& this.Any(i => i.Key.Lifetime?.Value is Lifetime.Singleton or Lifetime.Scoped or Lifetime.PerResolve);
this.Any(i => i.Key.Lifetime?.Value is Lifetime.Singleton or Lifetime.Scoped or Lifetime.PerResolve);

public IEnumerable<Variable> GetPerResolves() => this
.Where(i => i.Key.Lifetime?.Value == Lifetime.PerResolve)
Expand Down
2 changes: 1 addition & 1 deletion src/Pure.DI.Core/Features/Default.g.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ private static void Setup()
.Bind<global::Pure.DI.Owned<TT>>()
.As(Lifetime.PerBlock)
.To(ctx => {
ctx.Inject<IOwned>(out var owned);
ctx.Inject<Owned>(out var owned);
ctx.Inject<TT>(ctx.Tag, out var value);
return new Owned<TT>(value, owned);
})
Expand Down

0 comments on commit 5ec3b14

Please sign in to comment.