O quanto o Reflection é lento?
Vamos criar um pequeno exemplo para analisarmos a performance do Reflection. Esse exemplo terá uma classe simples que contém apenas 1 método:
namespace CustomNamespace
{
public interface ICustom
{
void CallMe(int x);
}
public class CustomClass : ICustom
{
public void CallMe(int x)
{
x = x + 10;
return;
}
}
}
Simples, não? Bom, vamos criar uma classe para consumir e checar a performance em situações de extress. Vamos criar objetos do tipo CustomClass em um loop e executar o método CallMe com valores randomicos de duas formas: 1. Sem Reflection, 2. Com Reflection
Para o teste com Reflection, vamos criar uma classe (ReflectionCalculator) com alguns métodos:
public partial class ReflectionCalculator
{
private Type dummyType;
public Type DummyType
{
get
{
dummyType = dummyType ??
CurrentAssembly.GetType("CustomNamespace.CustomClass",
true,
true);
return dummyType;
}
}
private MethodInfo method;
public MethodInfo Method
{
get
{
this.method = this.method ??
this.DummyType.GetMethod("CallMe", BindingFlags.Instance |
BindingFlags.Public, null, new Type[] { typeof(int) },
null);
return this.method;
}
}
private object dummyObject;
public object DummyObject
{
get
{
if (this.dummyObject == null)
{
dummyObject = Activator.CreateInstance(this.DummyType);
}
return dummyObject;
}
}
public void ReflectionBasedCall(int value)
{
this.Method.Invoke(this.DummyObject, new object[] { value });
}
public void NormalCall(int value)
{
this.MyClass = this.MyClass ?? new CustomClass();
this.MyClass.CallMe(20);
}
}
Então, se olhar para as duas chamadas ReflectionBasedCall e NormalCall verá que ambos fazem a mesma coisa. A abordagem de reflexão deve primeiro obter um Type usando Assembly.GetType (que evetualmente vasculha toda a hierarquia do objeto inteiro) e depois o GetMethod no tipo encontrado. Portanto quanto maior o tamanho do Assembly mais lento será. Por fim, usamos o Activator.CreateInstance para criar a instância do objeto.
Agora, e se executassemos os métodos 1.000.000 de vezes usando esse código e imprimissemos o tempo em segundos, ele irá se parecer com:
static void Main(string[] args)
{
var watcher = new System.Diagnostics.Stopwatch();
var calculator = new ReflectionCalculator();
watcher.Start();
for (int i = 0; i < 1000000; i++)
{
calculator.NormalCall(i);
}
watcher.Stop();
Console.WriteLine("Time Elapsed for Normal call : {0}",
watcher.Elapsed.TotalSeconds);
watcher.Reset();
watcher.Start();
for (int i = 0; i < 1000000; i++)
{
calculator.ReflectionBasedCall(i);
}
watcher.Stop();
Console.WriteLine("Time Elapsed for Reflection call : {0}",
watcher.Elapsed.TotalSeconds);
Console.ReadLine();
}
Abaixo, a saída da execução:
Reflection + Lambda Expression = Dynamic Delegates
Na versão 3.0 do Microsoft .Net Framework, juntamente com o Linq, foi introduzido o Lambda Expression, que é uma função anonima que pode conter expressões e funções e pode ser usada para criar Delegates ou tipos de árvores de expressão.
Criar delegates em runtime é atualmente a melhor maneira de fazer o código Reflection ser executado muito rápido. A idéia básica é criar delegates de cada MethodInfo que precisamos executar e eventualmente fazer cache dele em memória.
Para isso, precisamos construir um Action<> (no caso de método VOID) ou Func<> (no caso de método que retorne algo) para cada método:
public static Action<object, T> CallMethod(this MethodInfo methodInfo)
{
if (!methodInfo.IsPublic) return null;
var returnParameter = Expression.Parameter(typeof(object), "method");
var valueArgument = Expression.Parameter(typeof(T), "argument");
var setterCall = Expression.Call(Expression.ConvertChecked(returnParameter,
methodInfo.DeclaringType),
methodInfo,
Expression.Convert(valueArgument, typeof(T)));
return Expression.Lambda<Action<object, T>>(setterCall, returnParameter,
valueArgument).Compile();
}
A expressão lambda acima cria um delegate do tipo Action
(x, y) = return x.[method](y)
Com isso, podemos guardar na memória a referência construída dinamicamente:
private MethodInfo methodInfo;
public MethodInfo CachedMethodInfo
{
get
{
this.methodInfo = this.methodInfo ?? this.GetMethodInfo();
return this.methodInfo;
}
}
private MethodInfo GetMethodInfo()
{
Type myClass = this.CurrentAssembly.GetType("CustomNamespace.CustomClass",
true,
true);
return myClass.GetMethod("CallMe", BindingFlags.Instance |
BindingFlags.Public, null, new Type[] { typeof(int) }, null);
}
private Action<object, int> _methodcallDelegate = null;
public Action<object, int> MethodCallDelegate
{
get
{
this._methodcallDelegate = this._methodcallDelegate ??
this.CachedMethodInfo.CallMethod<int>();
return this._methodcallDelegate;
}
}
Vamos executar o código de teste mais uma vez para ver o quão eficiente é essa técnica:
Veja que a diferença em relação a uma abordagem hard-coded caiu drasticamente expandindo as possibilidades de criação de algoritmos genéricos e de alta performance.
Você tem dicas e truques? Compartilhe! Envie para social@apolineo.com.br que colocaremos no blog.
Fique a vontade também de enviar suas sugestões e críticas.
Download: Código Fonte
Até a próxima!
Nenhum comentário:
Postar um comentário