Added shared bind cache for improved performance and enhanced binder leak mitigation.

This commit is contained in:
ClearScript 2014-03-17 23:03:52 -04:00
Родитель 7df5fa298b
Коммит 1ea01c8490
5 изменённых файлов: 105 добавлений и 23 удалений

Просмотреть файл

@ -65,13 +65,15 @@ namespace Microsoft.ClearScript
{
internal class BindSignature : IEquatable<BindSignature>
{
private readonly Type context;
private readonly TargetInfo targetInfo;
private readonly string name;
private readonly Type[] typeArgs;
private readonly ArgInfo[] argData;
public BindSignature(HostTarget target, string name, Type[] typeArgs, object[] args)
public BindSignature(Type context, HostTarget target, string name, Type[] typeArgs, object[] args)
{
this.context = context;
targetInfo = new TargetInfo(target);
this.typeArgs = typeArgs;
this.name = name;
@ -94,6 +96,7 @@ namespace Microsoft.ClearScript
{
var accumulator = new HashAccumulator();
accumulator.Update(context);
targetInfo.UpdateHash(ref accumulator);
accumulator.Update(name);
@ -121,6 +124,11 @@ namespace Microsoft.ClearScript
return false;
}
if (context != that.context)
{
return false;
}
if (!targetInfo.Equals(that.targetInfo))
{
return false;

Просмотреть файл

@ -60,12 +60,14 @@
//
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using Microsoft.CSharp.RuntimeBinder;
using Microsoft.ClearScript.Util;
using Binder = Microsoft.CSharp.RuntimeBinder.Binder;
@ -74,6 +76,13 @@ namespace Microsoft.ClearScript
{
internal partial class HostItem
{
#region data
private static readonly ConcurrentDictionary<BindSignature, object> coreBindCache = new ConcurrentDictionary<BindSignature, object>();
private static long coreBindCount;
#endregion
#region internal members
private object InvokeMethod(string name, object[] args, object[] bindArgs)
@ -138,7 +147,8 @@ namespace Microsoft.ClearScript
// WARNING: BindSignature holds on to the specified typeArgs; subsequent modification
// will result in bugs that are difficult to diagnose. Create a copy if necessary.
var signature = new BindSignature(target, name, typeArgs, bindArgs);
var context = accessContext ?? engine.AccessContext;
var signature = new BindSignature(context, target, name, typeArgs, bindArgs);
MethodBindResult result;
object rawResult;
@ -148,7 +158,7 @@ namespace Microsoft.ClearScript
}
else
{
result = BindMethodInternal(name, typeArgs, args, bindArgs);
result = BindMethodInternal(context, name, typeArgs, args, bindArgs);
if (!result.IsPreferredMethod(name))
{
if (result is MethodBindSuccess)
@ -158,7 +168,7 @@ namespace Microsoft.ClearScript
foreach (var altName in GetAltMethodNames(name))
{
var altResult = BindMethodInternal(altName, typeArgs, args, bindArgs);
var altResult = BindMethodInternal(context, altName, typeArgs, args, bindArgs);
if (altResult.IsUnblockedMethod())
{
result = altResult;
@ -173,11 +183,32 @@ namespace Microsoft.ClearScript
return result;
}
private MethodBindResult BindMethodInternal(string name, Type[] typeArgs, object[] args, object[] bindArgs)
private MethodBindResult BindMethodInternal(Type context, string name, Type[] typeArgs, object[] args, object[] bindArgs)
{
var signature = new BindSignature(context, target, name, typeArgs, bindArgs);
MethodBindResult result;
object rawResult;
if (coreBindCache.TryGetValue(signature, out rawResult))
{
result = MethodBindResult.Create(name, rawResult, target, args);
}
else
{
result = BindMethodCore(context, name, typeArgs, args, bindArgs);
coreBindCache.TryAdd(signature, result.RawResult);
}
return result;
}
private MethodBindResult BindMethodCore(Type context, string name, Type[] typeArgs, object[] args, object[] bindArgs)
{
Interlocked.Increment(ref coreBindCount);
// create C# member invocation binder
const CSharpBinderFlags binderFlags = CSharpBinderFlags.InvokeSimpleName | CSharpBinderFlags.ResultDiscarded;
var binder = Binder.InvokeMember(binderFlags, name, typeArgs, accessContext ?? engine.AccessContext, CreateArgInfoEnum(bindArgs));
var binder = Binder.InvokeMember(binderFlags, name, typeArgs, context, CreateArgInfoEnum(bindArgs));
// perform default binding
var binding = DynamicHelpers.Bind((DynamicMetaObjectBinder)binder, target, bindArgs);
@ -275,6 +306,20 @@ namespace Microsoft.ClearScript
#endregion
#region unit test support
internal static void ResetCoreBindCount()
{
Interlocked.Exchange(ref coreBindCount, 0);
}
internal static long GetCoreBindCount()
{
return Interlocked.Read(ref coreBindCount);
}
#endregion
#region Nested type: MethodBindResult
private abstract class MethodBindResult

Просмотреть файл

@ -84,6 +84,8 @@ namespace Microsoft.ClearScript.Windows
{
#region data
private static readonly object nullDispatch = new DispatchWrapper(null);
private ActiveScriptWrapper activeScript;
private WindowsScriptEngineFlags engineFlags;
@ -428,7 +430,7 @@ namespace Microsoft.ClearScript.Windows
{
if (engineFlags.HasFlag(WindowsScriptEngineFlags.MarshalNullAsDispatch))
{
return new DispatchWrapper(null);
return nullDispatch;
}
return DBNull.Value;

Просмотреть файл

@ -110,50 +110,56 @@ namespace Microsoft.ClearScript.Test
};
{
var sig1 = new BindSignature(HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
var sig2 = new BindSignature(new HostVariable<string>(null), "foo", typeArgs1, args1);
var sig1 = new BindSignature(typeof(BindSignatureTest), HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
var sig2 = new BindSignature(typeof(ClearScriptTest), HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
AssertNotEqual(sig1, sig2);
}
{
var sig1 = new BindSignature(new HostVariable<string>(null), "foo", typeArgs1, args1);
var sig2 = new BindSignature(HostObject.Wrap("baz"), "foo", typeArgs1, args1);
var sig1 = new BindSignature(typeof(BindSignatureTest), HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
var sig2 = new BindSignature(typeof(BindSignatureTest), new HostVariable<string>(null), "foo", typeArgs1, args1);
AssertNotEqual(sig1, sig2);
}
{
var sig1 = new BindSignature(HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
var sig2 = new BindSignature(HostObject.Wrap("baz"), "foo", typeArgs1, args1);
var sig1 = new BindSignature(typeof(BindSignatureTest), new HostVariable<string>(null), "foo", typeArgs1, args1);
var sig2 = new BindSignature(typeof(BindSignatureTest), HostObject.Wrap("baz"), "foo", typeArgs1, args1);
AssertNotEqual(sig1, sig2);
}
{
var sig1 = new BindSignature(new HostVariable<string>(null), "foo", typeArgs1, args1);
var sig2 = new BindSignature(new HostVariable<string>("baz"), "foo", typeArgs1, args1);
var sig1 = new BindSignature(typeof(BindSignatureTest), HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
var sig2 = new BindSignature(typeof(BindSignatureTest), HostObject.Wrap("baz"), "foo", typeArgs1, args1);
AssertNotEqual(sig1, sig2);
}
{
var sig1 = new BindSignature(new HostVariable<string>("baz"), "foo", typeArgs1, args1);
var sig2 = new BindSignature(HostObject.Wrap("qux"), "foo", typeArgs1, args1);
var sig1 = new BindSignature(typeof(BindSignatureTest), new HostVariable<string>(null), "foo", typeArgs1, args1);
var sig2 = new BindSignature(typeof(BindSignatureTest), new HostVariable<string>("baz"), "foo", typeArgs1, args1);
AssertNotEqual(sig1, sig2);
}
{
var sig1 = new BindSignature(typeof(BindSignatureTest), new HostVariable<string>("baz"), "foo", typeArgs1, args1);
var sig2 = new BindSignature(typeof(BindSignatureTest), HostObject.Wrap("qux"), "foo", typeArgs1, args1);
AssertEqual(sig1, sig2);
}
{
var sig1 = new BindSignature(HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
var sig2 = new BindSignature(HostType.Wrap(typeof(string)), "bar", typeArgs1, args1);
var sig1 = new BindSignature(typeof(BindSignatureTest), HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
var sig2 = new BindSignature(typeof(BindSignatureTest), HostType.Wrap(typeof(string)), "bar", typeArgs1, args1);
AssertNotEqual(sig1, sig2);
}
{
var sig1 = new BindSignature(HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
var sig2 = new BindSignature(HostType.Wrap(typeof(string)), "foo", typeArgs2, args1);
var sig1 = new BindSignature(typeof(BindSignatureTest), HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
var sig2 = new BindSignature(typeof(BindSignatureTest), HostType.Wrap(typeof(string)), "foo", typeArgs2, args1);
AssertNotEqual(sig1, sig2);
}
{
var sig1 = new BindSignature(HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
var sig2 = new BindSignature(HostType.Wrap(typeof(string)), "foo", typeArgs1, args2);
var sig1 = new BindSignature(typeof(BindSignatureTest), HostType.Wrap(typeof(string)), "foo", typeArgs1, args1);
var sig2 = new BindSignature(typeof(BindSignatureTest), HostType.Wrap(typeof(string)), "foo", typeArgs1, args2);
AssertEqual(sig1, sig2);
}
}

Просмотреть файл

@ -379,6 +379,27 @@ namespace Microsoft.ClearScript.Test
results.CompiledAssembly.GetType("TestModule").InvokeMember("TestMethod", BindingFlags.InvokeMethod, null, null, MiscHelpers.GetEmptyArray<object>());
}
[TestMethod, TestCategory("BugFix")]
public void BugFix_CoreBindCache()
{
HostItem.ResetCoreBindCount();
engine.Dispose();
for (var i = 0; i < 10; i++)
{
using (engine = new V8ScriptEngine(V8ScriptEngineFlags.EnableDebugging))
{
engine.Script.host = new HostFunctions();
Assert.AreEqual(' ', engine.Evaluate("host.toChar(32)"));
Assert.AreEqual('A', engine.Evaluate("host.toChar(65)"));
Assert.AreEqual(0.0F, engine.Evaluate("host.toSingle(0)"));
Assert.AreEqual(1.0F, engine.Evaluate("host.toSingle(1)"));
}
}
Assert.AreEqual(2L, HostItem.GetCoreBindCount());
}
// ReSharper restore InconsistentNaming
#endregion