簡體   English   中英

C#speedup方法使用表達式調用泛型類

[英]C# speedup method call of a generic class using expressions

我需要調用泛型類的實例方法。 簽名如下所示:

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 |

執行此操作的方法是通過適當的泛型方法,將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

編寫泛型方法並使用Invoke的MakeGenericMethod來調用它。

將要調用的方法存儲在靜態變量中,以便GetMethod調用只需要發生一次。

然后在該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);
}

在我的測試中,這使它快了100多倍。

您應該簡單地創建一個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);
}

緩存委托也是個好主意:

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

您可以將其設置為Action<MemoryStream, object>並使用Expression.Convert()v類型從object更改為type

為了獲得性能提升,您可以將這些Action存儲在某些Type -keyed字典中(並發?),但表達式需要進行一些更改才能Create其中的處理程序。

添加另一個答案,因為這個與我的第一個明顯不同。

TLDR:添加非泛型接口,創建字典以緩存類型的處理程序,使用非泛型Write方法調用處理程序。


向ExplicitHandler添加非泛型接口,以便更容易與非泛型代碼進行交互。

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

在ExplicitHandler中實現非泛型Write方法以強制轉換為T並調用泛型Write

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

將處理程序緩存在字典中

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

在這里重寫你的來源: http//pastebin.com/hmfj2Gv2

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM