Ответ 1
Используете ли вы С# 4? Если это так, dynamic
может ускорить работу:
Action<object> hook = message => ((dynamic)handler).HandleMessage((dynamic)message);
Есть много сообщений об ускорении вызова отражения, примеры здесь:
Ускорение API Reflection с делегатом в .NET/С#
https://codeblog.jonskeet.uk/2008/08/09/making-reflection-fly-and-exploring-delegates/
и здесь:
Пример: Ускорение API Reflection с делегатом в .NET/С#
Мой вопрос об ускорении общих вызовов. Возможно ли это вообще?
У меня есть абстрактный класс и класс, который его реализует...
public abstract class EncasulatedMessageHandler<T> where T : Message
{
public abstract void HandleMessage(T message);
}
public class Handler : EncasulatedMessageHandler<MyMessageType>
{
public int blat = 0;
public override void HandleMessage(MyMessageType message) { blat++; }
}
Что я хочу сделать, это создать список этих классов обработчиков сообщений и быстро вызвать их HandleMessage()
В настоящий момент я делаю то, что примерно так:
object handler = Activator.CreateInstance(typeof(Handler)); // Ignore this, this is done up front.
MethodInfo method = type.GetMethod("HandleMessage", BindingFlags.Instance | BindingFlags.Public);
Action<object> hook = new Action<object>(delegate(object message)
{
method.Invoke(handler, new object[] { message });
});
// Then when I want to invoke it:
hook(new MyMessageType());
Это не все, но это важный материал...
Метод .Invoke очень медленный, я хотел бы сохранить общие параметры в классе, я понимаю, что могу заблокировать это для объекта и применить его в методе HandleMessage, но я стараюсь избегать этого.
Есть ли что-нибудь, что я могу сделать, чтобы ускорить это? Он в настоящее время на порядок медленнее прямых вызовов.
Любая помощь будет оценена.
Используете ли вы С# 4? Если это так, dynamic
может ускорить работу:
Action<object> hook = message => ((dynamic)handler).HandleMessage((dynamic)message);
Использование Delegate.CreateDelegate()
должно быть намного быстрее. Вы получите указатель на реальную функцию, а не делегат, который вызывает Invoke()
.
Попробуйте следующее:
object handler = Activator.CreateInstance(typeof(Handler));
var handlerType = handler.GetType();
var method = handlerType.GetMethod("HandleMessage", BindingFlags.Instance | BindingFlags.Public);
var paramType = handlerType.GetGenericArguments()[0];
// invoke the MakeHandleMessageDelegate method dynamically with paramType as the type parameter
// NB we're only doing this once
Action<object> hook = (Action<object>) this.GetType().GetMethod("MakeHandleMessageDelegate")
.MakeGenericMethod(paramType)
.Invoke(null, new [] { handler });
В том же классе добавьте следующий общий метод. Мы вызываем это динамически выше, потому что мы не знаем параметр типа во время компиляции.
public static Action<object> MakeHandleMessageDelegate<T>(object target)
{
var d = (Action<T>)Delegate.CreateDelegate(typeof(Action<T>), target, "HandleMessage");
// wrap the delegate another that simply casts the object parameter to the required type
return param => d((T)param);
}
Затем у вас есть делегат, который задает параметр требуемому типу, затем вызывает метод HandleMessage
.
Вы можете использовать Delegate::CreateDelegate
. Это значительно быстрее, чем Invoke()
.
var handler = Activator.CreateInstance(typeof(Handler));
var method = type.GetMethod("HandleMessage", BindingFlags.Instance | BindingFlags.Public);
var hook = (Action<object>)Delegate.CreateDelegate(typeof(Action<object>), handler, method);
// Then when you want to invoke it:
hook(new MyMessageType());
Не стесняйтесь оценивать его, но я уже скачал его раньше, и это было значительно быстрее.
Изменить. Я вижу вашу проблему сейчас, вы не можете сделать это так, как я предложил.
Вы можете использовать выражения для компиляции делегата, который выполняет вызов для вас, это будет очень быстро:
var type = typeof(Handler);
var instance = Activator.CreateInstance(type);
var method = type.GetMethod("HandleMessage", BindingFlags.Instance | BindingFlags.Public);
var originalType = type;
// Loop until we hit the type we want.
while (!(type.IsGenericType) || type.GetGenericTypeDefinition() != typeof(EncasulatedMessageHandler<>))
{
type = type.BaseType;
if(type == null)
throw new ArgumentOutOfRangeException("type");
}
var messageType = type.GetGenericArguments()[0]; // MyMessageType
// Use expression to create a method we can.
var instExpr = Expression.Parameter(typeof(object), "instance");
var paramExpr = Expression.Parameter(typeof(Message), "message");
// (Handler)instance;
var instCastExpr = Expression.Convert(instExpr, originalType);
// (MyMessageType)message
var castExpr = Expression.Convert(paramExpr, messageType);
// ((Handler)inst).HandleMessage((MyMessageType)message)
var invokeExpr = Expression.Call(instCastExpr, method, castExpr);
// if(message is MyMessageType) ((Handler)inst).HandleMessage((MyMessageType)message);
var ifExpr = Expression.IfThen(Expression.TypeIs(paramExpr, messageType), invokeExpr);
// (inst, message) = { if(message is MyMessageType) ((Handler)inst).HandleMessage((MyMessageType)message); }
var lambda = Expression.Lambda<Action<object, Message>>(ifExpr, instExpr, paramExpr);
var compiled = lambda.Compile();
Action<Message> hook = x => compiled(instance, x);
hook(new MyMessageType());
Изменить: Помимо моего примера Expression, также будет работать следующее: и это то, что я делаю в этих сценариях.
var instance = (IEncapsulatedMessageHandler)Activator.CreateInstance(typeof(Handler));
instance.HandleMessage(new MyMessageType());
public class Message { }
public class MyMessageType : Message { }
public interface IEncapsulatedMessageHandler
{
void HandleMessage(Message message);
}
public abstract class EncasulatedMessageHandler<T> : IEncapsulatedMessageHandler where T : Message
{
public abstract void HandleMessage(T message);
void IEncapsulatedMessageHandler.HandleMessage(Message message)
{
var msg = message as T;
if (msg != null)
HandleMessage(msg);
}
}
public class Handler : EncasulatedMessageHandler<MyMessageType>
{
public override void HandleMessage(MyMessageType message)
{
Console.WriteLine("Yo!");
}
}
Нет, это (к сожалению) невозможно. Отражение медленное и MethodInfo.Invoke() не является исключением. Не могли бы вы использовать (общие) интерфейсы и, следовательно, прямые вызовы?
Редактирование обновления: на самом деле стоит вспомнить, как это ускорить, но накладные расходы на кодирование огромны: вы можете использовать генерацию и компиляцию динамического кода. Это будет означать динамическое построение исходного кода, который будет вызывать метод без отражения, динамически компилируя и выполняя это. Это будет означать первоначальное влияние производительности для создания и компиляции классов, которые выполняют вашу работу, но затем у вас есть прямые вызовы для каждого последующего вызова.
Если вы знаете подпись, используйте Delegate.CreateDelegate
.
Если вы не знаете подпись, очень сложно получить что-то такое быстро. Если вам нужна скорость, то что бы вы ни делали, старайтесь избегать Delegate.DynamicInvoke
, который очень медленный.
(обратите внимание на то, что "медленный" здесь очень относительный. Убедитесь, что вам действительно нужно оптимизировать это. DynamicInvoke
- это что-то вроде 2.5 миллионов запросов в секунду (на моей машине), что очень вероятно достаточно быстро. больше напоминает 110 миллионов экземпляров в секунду и быстрее, чем Method.Invoke
.)
Я нашел статью, в которой обсуждается способ ее выполнения (быстро вызовите метод, не зная подписи во время компиляции). Вот моя версия реализации. Странная проблема заключается в том, что вы можете создать лямбда, которая представляет вызов, но вы не знали бы подпись этой лямбды и должны были бы назвать ее динамически (медленно). Но вместо этого вы можете испечь строго типизированный вызов в лямбда, причем лямбда представляет собой акт вызова, а не сам конкретный метод. (Лямбда заканчивается Func<object, object[], object>
, где вы передаете объект и некоторые значения и возвращаете возвращаемое значение.)
public static Func<object, object[], object> ToFastLambdaInvocationWithCache(
this MethodInfo pMethodInfo
) {
Func<object, object[], object> cached;
if (sLambdaExpressionsByMethodInfoCache.TryGetValue(pMethodInfo, out cached))
return cached;
var instanceParameterExpression = Expression.Parameter(typeof(object), "instance");
var argumentsParameterExpression = Expression.Parameter(typeof(object[]), "args");
var index = 0;
var argumentExtractionExpressions =
pMethodInfo
.GetParameters()
.Select(parameter =>
Expression.Convert(
Expression.ArrayAccess(
argumentsParameterExpression,
Expression.Constant(index++)
),
parameter.ParameterType
)
).ToList();
var callExpression = pMethodInfo.IsStatic
? Expression.Call(pMethodInfo, argumentExtractionExpressions)
: Expression.Call(
Expression.Convert(
instanceParameterExpression,
pMethodInfo.DeclaringType
),
pMethodInfo,
argumentExtractionExpressions
);
var endLabel = Expression.Label(typeof(object));
var finalExpression = pMethodInfo.ReturnType == typeof(void)
? (Expression)Expression.Block(
callExpression,
Expression.Return(endLabel, Expression.Constant(null)),
Expression.Label(endLabel, Expression.Constant(null))
)
: Expression.Convert(callExpression, typeof(object));
var lambdaExpression = Expression.Lambda<Func<object, object[], object>>(
finalExpression,
instanceParameterExpression,
argumentsParameterExpression
);
var compiledLambda = lambdaExpression.Compile();
sLambdaExpressionsByMethodInfoCache.AddOrReplace(pMethodInfo, compiledLambda);
return compiledLambda;
}