Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve first invoke performance #260

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 132 additions & 1 deletion src/UiPath.Workflow/Activities/ScriptingJitCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
using System.Activities.ExpressionParser;
using System.Activities.Internals;
using System.Activities.XamlIntegration;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.Collections;
using System.Runtime.Loader;
using System.Text;
using System.Threading;
Expand Down Expand Up @@ -36,6 +39,133 @@ public record ExpressionToCompile(string Code, IReadOnlyCollection<string> Impor
: CompilerInput(Code, ImportedNamespaces)
{ }

public sealed class CachedMetadataReferenceResolver : MetadataReferenceResolver
{
public static CachedMetadataReferenceResolver Default = new CachedMetadataReferenceResolver(ScriptMetadataResolver.Default);

ScriptMetadataResolver _resolver;

private class ResolveCacheKey
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use a record for this instead? It already has equals and hashcode implementations that do very similar things to what you're doing here.

{
private string _reference;
private string _baseFilePath;
private MetadataReferenceProperties _properties;

private readonly int _hashCode;

public ResolveCacheKey(string reference, string baseFilePath, MetadataReferenceProperties properties)
{
_reference = reference;
_baseFilePath = baseFilePath;
_properties = properties;

_hashCode = reference?.GetHashCode() ?? 0;
_hashCode = CombineHashCodes(_hashCode, _baseFilePath?.GetHashCode() ?? 0);
_hashCode = CombineHashCodes(_hashCode, properties.GetHashCode());
}

public override bool Equals(object obj)
{
if (obj is not ResolveCacheKey rtcKey || _hashCode != rtcKey._hashCode)
{
return false;
}

return _reference == rtcKey._reference &&
_baseFilePath == rtcKey._baseFilePath &&
_properties.Equals(rtcKey._properties);
}

public override int GetHashCode() => _hashCode;
}
ConcurrentDictionary<ResolveCacheKey, ImmutableArray<PortableExecutableReference>> _resolveCache = new ConcurrentDictionary<ResolveCacheKey, ImmutableArray<PortableExecutableReference>>();

private class ResolveMissingCacheKey
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could also be a record.

{
private MetadataReference _definition;
private AssemblyIdentity _referenceIdentity;

private readonly int _hashCode;

public ResolveMissingCacheKey(MetadataReference definition, AssemblyIdentity referenceIdentity)
{
_definition = definition;
this._referenceIdentity = referenceIdentity;

_hashCode = definition.GetHashCode();
_hashCode = CombineHashCodes(_hashCode, _referenceIdentity.GetHashCode());
}

public override bool Equals(object obj)
{
if (obj is not ResolveMissingCacheKey rtcKey || _hashCode != rtcKey._hashCode)
{
return false;
}

return _definition.Equals(rtcKey._definition) &&
_referenceIdentity.Equals(rtcKey._referenceIdentity);
}

public override int GetHashCode() => _hashCode;
}
ConcurrentDictionary<ResolveMissingCacheKey, PortableExecutableReference> _resolveMissingCache = new ConcurrentDictionary<ResolveMissingCacheKey, PortableExecutableReference>();

public CachedMetadataReferenceResolver(ScriptMetadataResolver resolver)
{
_resolver = resolver;
}

public override bool Equals(object other)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does the resolver need this if you're always using the singleton?

{
return ReferenceEquals(this, other) ||
other != null && other is CachedMetadataReferenceResolver &&
Equals(_resolver, ((CachedMetadataReferenceResolver)other)._resolver) &&
Equals(_resolveCache, ((CachedMetadataReferenceResolver)other)._resolveCache) &&
Equals(_resolveMissingCache, ((CachedMetadataReferenceResolver)other)._resolveMissingCache);
}

public override int GetHashCode()
{
return CombineHashCodes(_resolver.GetHashCode(),
CombineHashCodes(_resolveCache.GetHashCode(),
_resolveMissingCache.GetHashCode()));
}
private static int CombineHashCodes(int h1, int h2) => ((h1 << 5) + h1) ^ h2;

public override ImmutableArray<PortableExecutableReference> ResolveReference(string reference, string baseFilePath, MetadataReferenceProperties properties)
{
ImmutableArray<PortableExecutableReference> ret;

var cacheKey = new ResolveCacheKey(reference, baseFilePath, properties);

if (!_resolveCache.TryGetValue(cacheKey, out ret))
{
ret = _resolver.ResolveReference(reference, baseFilePath, properties);
_resolveCache.TryAdd(cacheKey, ret);
}

return ret;
}

public override bool ResolveMissingAssemblies => _resolver.ResolveMissingAssemblies;

public override PortableExecutableReference ResolveMissingAssembly(MetadataReference definition, AssemblyIdentity referenceIdentity)
{
PortableExecutableReference ret;

var cacheKey = new ResolveMissingCacheKey(definition, referenceIdentity);

if (!_resolveMissingCache.TryGetValue(cacheKey, out ret))
{
ret = _resolver.ResolveMissingAssembly(definition, referenceIdentity);
_resolveMissingCache.TryAdd(cacheKey, ret);
}

return ret;
}
}

public abstract class ScriptingJitCompiler : JustInTimeCompiler
{
protected ScriptingJitCompiler(HashSet<Assembly> referencedAssemblies)
Expand All @@ -55,7 +185,8 @@ public override LambdaExpression CompileExpression(ExpressionToCompile expressio
var options = ScriptOptions.Default
.WithReferences(MetadataReferences)
.WithImports(expressionToCompile.ImportedNamespaces)
.WithOptimizationLevel(OptimizationLevel.Release);
.WithOptimizationLevel(OptimizationLevel.Release)
.WithMetadataResolver(CachedMetadataReferenceResolver.Default);
var untypedExpressionScript = Create(expressionToCompile.Code, options);
var compilation = untypedExpressionScript.GetCompilation();
var syntaxTree = compilation.SyntaxTrees.First();
Expand Down