Skip to content

Commit

Permalink
Improved Resolve methods performance
Browse files Browse the repository at this point in the history
  • Loading branch information
NikolayPianikov committed Apr 12, 2024
1 parent d0690e2 commit 0e3fbc5
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ namespace Pure.DI.Benchmarks.Tests;
using Moq;

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

[Benchmark(Baseline = true)]
public List<IDisposable> ListAdd()
public List<IDisposable> Baseline()
{
var list = new List<IDisposable>();
for (var i = 0; i < Count; i++)
Expand All @@ -29,7 +29,7 @@ public List<IDisposable> ListAdd()
}

[Benchmark]
public IOwned OwnedAdd()
public IOwned AddDisposable()
{
var owned = new Owned();
for (var i = 0; i < Count; i++)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// ReSharper disable SuggestVarOrType_BuiltInTypes
// ReSharper disable MemberCanBeMadeStatic.Local
// ReSharper disable RedundantNameQualifier
// ReSharper disable UnusedMethodReturnValue.Local
#pragma warning disable CS8500
namespace Pure.DI.Benchmarks.Tests;

Expand All @@ -10,7 +11,7 @@ namespace Pure.DI.Benchmarks.Tests;

[SuppressMessage("Performance", "CA1822:Mark members as static")]
[SuppressMessage("Performance", "CA1859:Use concrete types when possible for improved performance")]
public class GetBenchmark
public class ResolveBenchmark
{
private const int MaxItems = 10;
private static readonly Pair<Type, string>[] Pairs;
Expand All @@ -28,7 +29,7 @@ public class GetBenchmark
private static readonly Type Key10;
private static readonly Dictionary<Type, string> Dictionary;

static GetBenchmark()
static ResolveBenchmark()
{
var data = CreatePairs();
Divisor = Buckets<Type, string>.GetDivisor((uint)data.Length);
Expand All @@ -52,7 +53,7 @@ static GetBenchmark()
}

[Benchmark(Baseline = true, OperationsPerInvoke = 10)]
public void Dict()
public void Baseline()
{
Dictionary.TryGetValue(Key1, out _);
Dictionary.TryGetValue(Key2, out _);
Expand All @@ -67,7 +68,7 @@ public void Dict()
}

[Benchmark(OperationsPerInvoke = 10)]
public string Resolve()
public void Resolve()
{
Resolve(Key1);
Resolve(Key2);
Expand All @@ -80,18 +81,27 @@ public string Resolve()
Resolve(Key9);
Resolve(Key10);
}


[MethodImpl(MethodImplOptions.AggressiveInlining)]
private string Resolve(global::System.Type type)
{
var index = (int)(BucketSize * ((uint)global::System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(type) % Divisor));
ref var pair = ref Pairs[index];
return pair.Key == type ? pair.Value : ResolveNoInlining(type, index);
}

[MethodImpl(MethodImplOptions.NoInlining)]
private string ResolveNoInlining(global::System.Type type, int index)
{
var finish = index + BucketSize;
do {
while (++index < finish)
{
ref var pair = ref Pairs[index];
if (pair.Key == type)
{
return pair.Value;
}
} while (++index < finish);
}

throw new global::System.InvalidOperationException($"Cannot resolve composition root of type {type}.");
}
Expand Down
83 changes: 67 additions & 16 deletions src/Pure.DI.Core/Core/Code/ApiMembersBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,20 @@ public CompositionCode Build(CompositionCode composition)
apiCode);

membersCounter++;

apiCode.AppendLine();

if (resolvers.Length > 0)
{
CreateObjectConflictsResolver(resolvers,
$"{Names.SystemNamespace}Type type",
Names.ResolveMethodName,
"this",
false,
apiCode);

membersCounter++;
}

apiCode.AppendLine();
if (isCommentsEnabled)
{
Expand All @@ -109,6 +122,19 @@ public CompositionCode Build(CompositionCode composition)
apiCode);

membersCounter++;
apiCode.AppendLine();

if (resolvers.Length > 0)
{
CreateObjectConflictsResolver(resolvers,
$"{Names.SystemNamespace}Type type, object? tag",
Names.ResolveByTagMethodName,
"this, tag",
true,
apiCode);

membersCounter++;
}
}

if (composition.Source.Source.Hints is { IsOnNewInstanceEnabled: true, IsOnNewInstancePartial: true })
Expand Down Expand Up @@ -155,32 +181,57 @@ private void CreateObjectResolverMethod(
LinesBuilder code)
{
buildTools.AddPureHeader(code);
code.AppendLine($"[{Names.MethodImplAttribute}(({Names.MethodImplOptions})0x100)]");
code.AppendLine($"{methodModifiers} object {methodName}({methodArgs})");
code.AppendLine("{");
using (code.Indent())
{
var divisor = Buckets<object, object>.GetDivisor((uint)resolvers.Count);
if (resolvers.Any())
if (resolvers.Count > 0)
{
code.AppendLine($"var index = (int)({Names.BucketSizeFieldName} * ((uint){Names.SystemNamespace}Runtime.CompilerServices.RuntimeHelpers.GetHashCode(type) % {divisor}));");
code.AppendLine($"var finish = index + {Names.BucketSizeFieldName};");
code.AppendLine("do {");
code.AppendLine($"ref var pair = ref {Names.BucketsFieldName}[index];");
code.AppendLine($"return pair.Key == type ? pair.Value.{resolveMethodName}({resolveMethodArgs}) : Resolve{Names.Salt}(type, index);");
}
else
{
code.AppendLine($"throw new {Names.SystemNamespace}InvalidOperationException($\"{Names.CannotResolve} {(byTag ? "\\\"{tag}\\\" " : "")}of type {{type}}.\");");
}
}

code.AppendLine("}");
}

private void CreateObjectConflictsResolver(
IReadOnlyCollection<ResolverInfo> resolvers,
string methodArgs,
string resolveMethodName,
string resolveMethodArgs,
bool byTag,
LinesBuilder code)
{
code.AppendLine($"[{Names.MethodImplAttribute}(({Names.MethodImplOptions})0x8)]");
code.AppendLine($"private object Resolve{Names.Salt}({methodArgs}, int index)");
code.AppendLine("{");
using (code.Indent())
{
code.AppendLine($"var finish = index + {Names.BucketSizeFieldName};");
code.AppendLine("while (++index < finish)");
code.AppendLine("{");
using (code.Indent())
{
code.AppendLine($"ref var pair = ref {Names.BucketsFieldName}[index];");
code.AppendLine("if (pair.Key == type)");
code.AppendLine("{");
using (code.Indent())
{
code.AppendLine($"ref var pair = ref {Names.BucketsFieldName}[index];");
code.AppendLine("if (pair.Key == type)");
code.AppendLine("{");
using (code.Indent())
{
code.AppendLine($"return pair.Value.{resolveMethodName}({resolveMethodArgs});");
}

code.AppendLine("}");
code.AppendLine($"return pair.Value.{resolveMethodName}({resolveMethodArgs});");
}

code.AppendLine("} while (++index < finish);");
code.AppendLine();
code.AppendLine("}");
}

code.AppendLine("}");
code.AppendLine();

code.AppendLine($"throw new {Names.SystemNamespace}InvalidOperationException($\"{Names.CannotResolve} {(byTag ? "\\\"{tag}\\\" " : "")}of type {{type}}.\");");
}
Expand Down
6 changes: 6 additions & 0 deletions src/Pure.DI.Core/Core/Code/RootMethodsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ private void BuildRoot(CompositionCode composition, Root root)
}

name.Append(rootArgsStr);
if (root.IsMethod)
{
code.AppendLine($"[{Names.MethodImplAttribute}(({Names.MethodImplOptions})0x100)]");
}

code.AppendLine(name.ToString());

if (typeArgs.Count > 0)
Expand Down Expand Up @@ -144,6 +149,7 @@ private void BuildRoot(CompositionCode composition, Root root)
if (!root.IsMethod)
{
buildTools.AddPureHeader(code);
code.AppendLine($"[{Names.MethodImplAttribute}(({Names.MethodImplOptions})0x100)]");
code.AppendLine("get");
code.AppendLine("{");
indentToken = code.Indent();
Expand Down

0 comments on commit 0e3fbc5

Please sign in to comment.