C#speedup方法使用表达式调用泛型类

时间:2016-09-09 10:52:17

标签: c# .net generics lambda

我需要调用泛型类的实例方法。签名如下所示:

public class HandlerFactory
{
    public static IHandler<T> Create<T>();
}

public interface IHandler<T>
{
    T Read(Stream s);

    void Write(Stream s, T v);
}

我设法通过使用表达式和DynamicInvoke来使其工作。可悲的是,DynamicInvoke的性能并不是那么好。我无法将委托转换为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();
    }
}

请注意,我没有对lambda生成进行基准测试,只调用了DynamicInvoke。

有没有办法用更快的速度替换DynamicInvoke?

更新:我评估了包含代码示例的3个答案,并选择使用Lasse V. Karlsen答案因为简单。 (关于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 |

5 个答案:

答案 0 :(得分:4)

执行此操作的方法是通过适当的通用方法,将演员从object包装到T,并跳过整个动态调用。

从您在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);
        };
    }
}

在我的机器上,您的代码与上面的代码一样执行:

  

你的测试:1285ms
  我的测试:20ms
  明确:4ms

答案 1 :(得分:3)

编写一个泛型方法,并使用Invoke的MakeGenericMethod来调用它。

将要调用的方法存储在静态变量中,这样GetMethod调用只需要发生一次。

然后在该MethodInfo上调用MakeGenericMethod并在结果上调用。

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);
}

在我的测试中,这使它快了100多倍。

答案 2 :(得分:1)

您只需创建一个 public partial class My_vwCustomer : DbMigration { public override void Up() { CreateTable( "dbo.vwCustomers", c => new { CustomerID = c.Int(nullable: false, identity: true), FirstName = c.String(), }) .PrimaryKey(t => t.CustomerID); } public override void Down() { DropTable("dbo.vwCustomers"); } }

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();
}

缓存委托也是个好主意:

static void Write(MemoryStream s, object value)
{
    var action = GetWriteDelegateForType(value.GetType());
    action(s, value);
}

答案 3 :(得分:0)

您可以将其设为Action<MemoryStream, object>,只需使用Expression.Convert()v的类型从object更改为type

为了获得性能提升,您可以将Action存储在某些Type - 键控字典(并发?)中,但这些表达式需要对Create内部处理程序进行一些更改他们。

答案 4 :(得分:0)

添加另一个答案,因为这个答案与我的第一个答案明显不同。

TLDR:添加非泛型接口,创建字典以缓存类型的处理程序,使用非泛型Write方法调用处理程序。

向ExplicitHandler添加一个非泛型接口,以便更容易与非泛型代码进行交互。

import random

a = [0,1,2,3,4,5,6,7,8,9]
b = [0,1,4,9,16,25,36,49,64,81]

frac = 0.2  # how much of a/b do you want to exclude

new_a, new_b = [], []

for i in range(len(a)):
    if random.random()>frac:  # with probability, add an element from `a` and `b` to the output
        new_a.append(a[i])
        new_b.append(b[i])

在ExplicitHandler中实现非泛型Write方法以强制转换为T并调用泛型Write

public interface IHandler
{
    void Write(Stream s, object v);
}

在字典中缓存处理程序

    void IHandler.Write(Stream s, object v)
    {
        Write(s, (T)v);
    }

在此处重写您的来源:http://pastebin.com/hmfj2Gv2