私はプロキシに取り組んでおり、参照型パラメータを持つジェネリック クラスの場合、非常に遅くなりました。特にジェネリック メソッドの場合 (約 400 ミリ秒に対して、null を返した単純なジェネリック メソッドの場合は 3200 ミリ秒)。生成されたクラスを C# で書き直した場合にどのように動作するかを試してみることにしました。パフォーマンスははるかに良く、非ジェネリック クラス コードとほぼ同じパフォーマンスでした。
これが私が書いたC#クラスです:: (名前付けスキームによって変更したことに注意してください。
namespace TestData
{
public class TestClassProxy<pR> : TestClass<pR>
{
private InvocationHandler<Func<TestClass<pR>, object>> _0_Test;
private InvocationHandler<Func<TestClass<pR>, pR, GenericToken, object>> _1_Test;
private static readonly InvocationHandler[] _proxy_handlers = new InvocationHandler[] {
new InvocationHandler<Func<TestClass<pR>, object>>(new Func<TestClass<pR>, object>(TestClassProxy<pR>.s_0_Test)),
new GenericInvocationHandler<Func<TestClass<pR>, pR, GenericToken, object>>(typeof(TestClassProxy<pR>), "s_1_Test") };
public TestClassProxy(InvocationHandler[] handlers)
{
if (handlers == null)
{
throw new ArgumentNullException("handlers");
}
if (handlers.Length != 2)
{
throw new ArgumentException("Handlers needs to be an array of 2 parameters.", "handlers");
}
this._0_Test = (InvocationHandler<Func<TestClass<pR>, object>>)(handlers[0] ?? _proxy_handlers[0]);
this._1_Test = (InvocationHandler<Func<TestClass<pR>, pR, GenericToken, object>>)(handlers[1] ?? _proxy_handlers[1]);
}
private object __0__Test()
{
return base.Test();
}
private object __1__Test<T>(pR local1) where T:IConvertible
{
return base.Test<T>(local1);
}
public static object s_0_Test(TestClass<pR> class1)
{
return ((TestClassProxy<pR>)class1).__0__Test();
}
public static object s_1_Test<T>(TestClass<pR> class1, pR local1) where T:IConvertible
{
return ((TestClassProxy<pR>)class1).__1__Test<T>(local1);
}
public override object Test()
{
return this._0_Test.Target(this);
}
public override object Test<T>(pR local1)
{
return this._1_Test.Target(this, local1, GenericToken<T>.Token);
}
}
}
これは、生成されたプロキシと同じ IL にリリース モードでコンパイルされます。
namespace TestData
{
public class TestClass<R>
{
public virtual object Test()
{
return default(object);
}
public virtual object Test<T>(R r) where T:IConvertible
{
return default(object);
}
}
}
1 つの例外がありました。生成された型に beforefieldinit 属性を設定していませんでした。次の属性を設定していました::public auto ansi
beforefieldinit を使用するとパフォーマンスが大幅に向上するのはなぜですか?
(他の唯一の違いは、パラメーターに名前を付けていなかったことです。これは、物事の壮大なスキームでは実際には問題ではありませんでした。メソッドとフィールドの名前は、実際のメソッドとの衝突を避けるためにスクランブルされています。GenericToken と InvocationHandlers は、無関係な実装の詳細です。引数のために、
GenericToken は文字通り、型付きデータ ホルダーとして使用され、ハンドラーに "T" を送信できるようにします
InvocationHandler はデリゲート フィールド ターゲットの単なるホルダーであり、実際の実装の詳細はありません。
GenericInvocationHandler は、DLR のようなコールサイト手法を使用して、渡されたさまざまな汎用引数を処理するために必要に応じてデリゲートを書き換えます)。
編集:: これがテストハーネスです::
private static void RunTests(int count = 1 << 24, bool displayResults = true)
{
var tests = Array.FindAll(Tests, t => t != null);
var maxLength = tests.Select(x => GetMethodName(x.Method).Length).Max();
for (int j = 0; j < tests.Length; j++)
{
var action = tests[j];
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < count; i++)
{
action();
}
sw.Stop();
if (displayResults)
{
Console.WriteLine("{2} {0}: {1}ms", GetMethodName(action.Method).PadRight(maxLength),
((int)sw.ElapsedMilliseconds).ToString(), j);
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}
private static string GetMethodName(MethodInfo method)
{
return method.IsGenericMethod
? string.Format(@"{0}<{1}>", method.Name, string.Join<Type>(",", method.GetGenericArguments()))
: method.Name;
}
そして、テストでは次のことを行います::
Tests[0] = () => proxiedTestClass.Test();
Tests[1] = () => proxiedTestClass.Test<string>("2");
Tests[2] = () => handClass.Test();
Tests[3] = () => handClass.Test<string>("2");
RunTests(100, false);
RunTests();
Tests は でFunc<object>[20]
、proxiedTestClass
はアセンブリによって生成されたクラスで、handClass
は手動で生成したクラスです。RunTests は 2 回呼び出されます。1 回目は「ウォームアップ」のため、もう 1 回は実行して画面に出力するためです。このコードは主に Jon Skeet の投稿から引用しました。