[英]C# speedup method call of a generic class using expressions
I need to call an instance method of a generic class. 我需要调用泛型类的实例方法。 The signature looks like this:
签名如下所示:
public class HandlerFactory
{
public static IHandler<T> Create<T>();
}
public interface IHandler<T>
{
T Read(Stream s);
void Write(Stream s, T v);
}
I managed to get it working by using expressions and DynamicInvoke. 我设法通过使用表达式和DynamicInvoke使其工作。 Sadly the performance of DynamicInvoke isn't that great.
可悲的是,DynamicInvoke的性能并不是那么好。 I can't cast the delegate to an
Action<MemoryStream, T>
because I don't know the type at compile time. 我无法将委托转换为
Action<MemoryStream, T>
因为我不知道编译时的类型。
public class Test
{
public static void Write(MemoryStream s, object value)
{
var del = GetWriteDelegateForType(value.GetType());
// TODO: How to make this faster?
del.DynamicInvoke(s, value);
}
private static object GetHandlerForType(Type type)
{
var expr = Expression.Call(typeof(HandlerFactory), "Create", new[] { type });
var createInstanceLambda = Expression.Lambda<Func<object>>(expr).Compile();
return createInstanceLambda();
}
private static Delegate GetWriteDelegateForType(Type type)
{
var handlerObj = GetHandlerForType(type);
var methodInfo = handlerObj.GetType().GetMethod("Write", new[] { typeof(MemoryStream), type });
var arg1 = Expression.Parameter(typeof(MemoryStream), "s");
var arg2 = Expression.Parameter(type, "v");
var handlerObjConstant = Expression.Constant(handlerObj);
var methodCall = Expression.Call(handlerObjConstant, methodInfo, arg1, arg2);
var lambda = Expression.Lambda(methodCall, arg1, arg2);
return lambda.Compile();
}
}
Please note, I didn't benchmark the lambda generation, just the call to DynamicInvoke. 请注意,我没有对lambda生成进行基准测试,只调用了DynamicInvoke。
Is there any way to replace DynamicInvoke with something faster? 有没有办法用更快的东西替换DynamicInvoke?
Update: I evaluated the 3 answers which contained code samples and choose to go with Lasse V. Karlsen answer because of the simplicity. 更新:我评估了包含代码示例的3个答案,并选择与Lasse V. Karlsen一起回答因为简单。 (Note on Grax's code: despite caching the MakeGenericMethod call it seems to be way slower than wrapping Invoke in a delegate)
(注意Grax的代码:尽管缓存了MakeGenericMethod调用,但它似乎比在委托中包装Invoke慢)
Method | Median | StdDev |
------------------- |-------------- |----------- |
MyLambda | 1,133.2459 ns | 25.1972 ns |
ExplicitCall | 0.6450 ns | 0.0256 ns |
Test2DelegateLasse | 10.6032 ns | 0.2141 ns |
LambdaGroo | 10.7274 ns | 0.1099 ns |
InvokeGrax | 349.9428 ns | 14.6841 ns |
The way to do this is to go through a proper generic method, wrapping up a cast from object
to the T
, and skipping the entire dynamic invoke. 执行此操作的方法是通过适当的泛型方法,将
object
转换包装到T
,并跳过整个动态调用。
From your code in the pastebin, here's a new version of your Test class: 从您在pastebin中的代码,这是Test类的新版本:
public class Test2
{
private static readonly Action<MemoryStream, object> del;
static Test2()
{
var genericCreateMethod = typeof(Test2).GetMethod("CreateWriteDelegate", BindingFlags.Static | BindingFlags.NonPublic);
var specificCreateMethod = genericCreateMethod.MakeGenericMethod(typeof(Model));
del = (Action<MemoryStream, object>)specificCreateMethod.Invoke(null, null);
}
public static void Write(MemoryStream s, object value)
{
del(s, value);
}
private static Action<MemoryStream, object> CreateWriteDelegate<T>()
{
var handler = HandlerFactory.Create<T>();
return delegate (MemoryStream s, object value)
{
handler.Write(s, (T)value);
};
}
}
On my machine your code, with the above as well executes as: 在我的机器上,您的代码与上面的代码一样执行:
Your test: 1285ms
你的测试:1285ms
My test: 20ms我的测试:20ms
Explicit: 4ms明确:4ms
Write a generic method and use MakeGenericMethod with Invoke to call it. 编写泛型方法并使用Invoke的MakeGenericMethod来调用它。
Store the method you want to call in a static variable so the GetMethod call only needs to happen once. 将要调用的方法存储在静态变量中,以便GetMethod调用只需要发生一次。
Then call MakeGenericMethod on that MethodInfo and Invoke on the result. 然后在该MethodInfo上调用MakeGenericMethod并在结果上调用Invoke。
private static MethodInfo GenericWriteMethod =
typeof(Test).GetMethod("GenericWrite", BindingFlags.NonPublic | BindingFlags.Static);
public static void Write(MemoryStream s, object value)
{
GenericWriteMethod
.MakeGenericMethod(value.GetType())
.Invoke(null, new object[] { s, value });
}
private static void GenericWrite<T>(MemoryStream s, T value)
{
HandlerFactory.Create<T>().Write(s, value);
}
In my testing, this made it over 100 times faster. 在我的测试中,这使它快了100多倍。
You should simply create an Action<Stream, object>
instead: 您应该简单地创建一个
Action<Stream, object>
:
static Action<Stream, object> GetWriteDelegateForType(Type type)
{
// get the actual generic method
var handlerObj = GetHandlerForType(type);
var methodInfo = handlerObj
.GetType()
.GetMethod("Write", new[] { typeof(MemoryStream), type });
// but use (Stream, object) parameters instead
var streamArg = Expression.Parameter(typeof(Stream), "s");
var objectArg = Expression.Parameter(typeof(object), "v");
// this will cast object to T
var tCast = Expression.Convert(objectArg, type);
var handlerObjConstant = Expression.Constant(handlerObj);
var body = Expression.Call(handlerObjConstant, methodInfo, streamArg, tCast);
var lambda = Expression.Lambda<Action<Stream, object>>(body, streamArg, objectArg);
// and compile to an actual Action<Stream, object>
return lambda.Compile();
}
And then you just call it like a plain delegate: 然后你就像一个普通的代表那样称呼它:
static void Write(MemoryStream s, object value)
{
var action = GetWriteDelegateForType(value.GetType());
action(s, value);
}
It would be also good idea to cache the delegate: 缓存委托也是个好主意:
static readonly ConcurrentDictionary<Type, Action<Stream, object>> _cache =
new ConcurrentDictionary<Type, Action<Stream, object>>();
static void Write(MemoryStream s, object value)
{
var type = value.GetType();
var action = _cache.GetOrAdd(type, GetWriteDelegateForType);
action(s, value);
}
You could just make it an Action<MemoryStream, object>
and simply use Expression.Convert()
to change type of v
from object
to type
. 您可以将其设置为
Action<MemoryStream, object>
并使用Expression.Convert()
将v
类型从object
更改为type
。
For performance gain, you could just store those Action
s in some Type
-keyed dictionary (concurrent?), but the expressions would require some changes to Create
the the handlers inside of them. 为了获得性能提升,您可以将这些
Action
存储在某些Type
-keyed字典中(并发?),但表达式需要进行一些更改才能Create
其中的处理程序。
Adding another answer because this one is significantly different than my first one. 添加另一个答案,因为这个与我的第一个明显不同。
TLDR: Add a non-generic interface, create a dictionary to cache the handler for a type, call the handler using a non-generic Write method. TLDR:添加非泛型接口,创建字典以缓存类型的处理程序,使用非泛型Write方法调用处理程序。
Add a non-generic interface to ExplicitHandler to make it easier to interact with in non-generic code. 向ExplicitHandler添加非泛型接口,以便更容易与非泛型代码进行交互。
public interface IHandler
{
void Write(Stream s, object v);
}
Implement the non-generic Write method in ExplicitHandler to cast to T and call the generic Write 在ExplicitHandler中实现非泛型Write方法以强制转换为T并调用泛型Write
void IHandler.Write(Stream s, object v)
{
Write(s, (T)v);
}
Cache the handlers in a dictionary 将处理程序缓存在字典中
public class Test
{
static Dictionary<Type, IHandler> delegates = new Dictionary<Type, IHandler>();
public static void Write(MemoryStream s, object value)
{
IHandler handler;
var type = value.GetType();
if (!delegates.TryGetValue(type, out handler))
{
handler = (IHandler)typeof(HandlerFactory).GetMethod(nameof(HandlerFactory.Create)).MakeGenericMethod(type).Invoke(null, null);
delegates[type] = handler;
}
handler.Write(s, value);
}
}
Rewrite of your source here: http://pastebin.com/hmfj2Gv2 在这里重写你的来源: http : //pastebin.com/hmfj2Gv2
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.