diff --git a/benchmarks/Pure.DI.Benchmarks/OwnedBenchmark.cs b/benchmarks/Pure.DI.Benchmarks/OwnedBenchmark.cs new file mode 100644 index 00000000..9dc6f87e --- /dev/null +++ b/benchmarks/Pure.DI.Benchmarks/OwnedBenchmark.cs @@ -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(); + + [Benchmark(Baseline = true)] + public List ListAdd() + { + var list = new List(); + 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; + } +} \ No newline at end of file diff --git a/benchmarks/Pure.DI.Benchmarks/Pure.DI.Benchmarks.csproj b/benchmarks/Pure.DI.Benchmarks/Pure.DI.Benchmarks.csproj index 8ffd140b..8b3f4659 100644 --- a/benchmarks/Pure.DI.Benchmarks/Pure.DI.Benchmarks.csproj +++ b/benchmarks/Pure.DI.Benchmarks/Pure.DI.Benchmarks.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Pure.DI.Core/Components/Api.g.cs b/src/Pure.DI.Core/Components/Api.g.cs index 2a0bb7dc..06b4b810 100644 --- a/src/Pure.DI.Core/Components/Api.g.cs +++ b/src/Pure.DI.Core/Components/Api.g.cs @@ -1102,10 +1102,8 @@ 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 _disposables = new global::System.Collections.Generic.List(); + /// /// Adds a disposable instance. /// @@ -1113,46 +1111,17 @@ internal partial class Owned : global::Pure.DI.IOwned [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); } } - + /// - [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()); + for (int i = disposables.Count - 1; i >= 0; i--) { var disposable = disposables[i]; try @@ -1189,13 +1158,7 @@ public DebugView(global::Pure.DI.Owned owned) { get { - global::System.Collections.Generic.List list = new global::System.Collections.Generic.List(_owned._count); - for (int i = 0; i < _owned._count; i++) - { - list.Add(_owned._disposables[i]); - } - - return list; + return _owned._disposables; } } } diff --git a/src/Pure.DI.Core/Core/Code/BuildTools.cs b/src/Pure.DI.Core/Core/Code/BuildTools.cs index 07bdfab7..f68383fd 100644 --- a/src/Pure.DI.Core/Core/Code/BuildTools.cs +++ b/src/Pure.DI.Core/Core/Code/BuildTools.cs @@ -51,7 +51,9 @@ public IEnumerable 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) @@ -59,10 +61,25 @@ public IEnumerable OnCreated(BuildContext ctx, Variable variable) .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( @@ -71,11 +88,16 @@ public IEnumerable 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 + { + new(0, $"{Names.OnNewInstanceMethodName}<{typeResolver.Resolve(variable.InstanceType)}>(ref {variable.VariableName}, {tag.ValueToString()}, {variable.Node.Lifetime.ValueToString()});") + }; + + lines.AddRange(code.Lines); return lines; } diff --git a/src/Pure.DI.Core/Core/Code/CompositionBuilder.cs b/src/Pure.DI.Core/Core/Code/CompositionBuilder.cs index 7dee7b69..b350b7d7 100644 --- a/src/Pure.DI.Core/Core/Code/CompositionBuilder.cs +++ b/src/Pure.DI.Core/Core/Code/CompositionBuilder.cs @@ -18,6 +18,7 @@ public CompositionCode Build(DependencyGraph graph) var map = new VariablesMap(); var allArgs = new HashSet(); var isThreadSafe = false; + var isThreadSafeEnabled = graph.Source.Hints.IsThreadSafeEnabled; foreach (var root in graph.Roots.Values) { cancellationToken.ThrowIfCancellationRequested(); @@ -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(); } diff --git a/src/Pure.DI.Core/Core/Code/VariablesMap.cs b/src/Pure.DI.Core/Core/Code/VariablesMap.cs index d4993a58..4a33112c 100644 --- a/src/Pure.DI.Core/Core/Code/VariablesMap.cs +++ b/src/Pure.DI.Core/Core/Code/VariablesMap.cs @@ -25,8 +25,7 @@ public IEnumerable 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 GetPerResolves() => this .Where(i => i.Key.Lifetime?.Value == Lifetime.PerResolve) diff --git a/src/Pure.DI.Core/Features/Default.g.cs b/src/Pure.DI.Core/Features/Default.g.cs index e312d13f..5bea645a 100644 --- a/src/Pure.DI.Core/Features/Default.g.cs +++ b/src/Pure.DI.Core/Features/Default.g.cs @@ -25,7 +25,7 @@ private static void Setup() .Bind>() .As(Lifetime.PerBlock) .To(ctx => { - ctx.Inject(out var owned); + ctx.Inject(out var owned); ctx.Inject(ctx.Tag, out var value); return new Owned(value, owned); })