Güncelleme: Önceden derlenmiş ve tembel derlenmiş karşılaştırmalar eklendi
Güncelleme 2: Anlaşıldı, yanılıyorum. Tam ve doğru bir cevap için Eric Lippert'in gönderisine bakın. Bunu kıyaslama rakamları uğruna burada bırakıyorum
* Güncelleme 3: Mark Gravell'in bu soruya verdiği cevaba göre IL-Emitted ve Lazy IL-Emitted benchmarkları eklendi .
Bildiğim dynamic
kadarıyla , anahtar kelimenin kullanımı kendi başına çalışma zamanında fazladan bir derlemeye neden olmaz (dinamik değişkenlerinizi hangi tür nesnelerin desteklediğine bağlı olarak belirli koşullar altında yapabileceğini düşünmeme rağmen).
Performansla ilgili olarak dynamic
, doğal olarak bir miktar ek yük getirir, ancak düşündüğünüz kadar değil. Örneğin, şuna benzeyen bir kıyaslama yaptım:
void Main()
{
Foo foo = new Foo();
var args = new object[0];
var method = typeof(Foo).GetMethod("DoSomething");
dynamic dfoo = foo;
var precompiled =
Expression.Lambda<Action>(
Expression.Call(Expression.Constant(foo), method))
.Compile();
var lazyCompiled = new Lazy<Action>(() =>
Expression.Lambda<Action>(
Expression.Call(Expression.Constant(foo), method))
.Compile(), false);
var wrapped = Wrap(method);
var lazyWrapped = new Lazy<Func<object, object[], object>>(() => Wrap(method), false);
var actions = new[]
{
new TimedAction("Direct", () =>
{
foo.DoSomething();
}),
new TimedAction("Dynamic", () =>
{
dfoo.DoSomething();
}),
new TimedAction("Reflection", () =>
{
method.Invoke(foo, args);
}),
new TimedAction("Precompiled", () =>
{
precompiled();
}),
new TimedAction("LazyCompiled", () =>
{
lazyCompiled.Value();
}),
new TimedAction("ILEmitted", () =>
{
wrapped(foo, null);
}),
new TimedAction("LazyILEmitted", () =>
{
lazyWrapped.Value(foo, null);
}),
};
TimeActions(1000000, actions);
}
class Foo{
public void DoSomething(){}
}
static Func<object, object[], object> Wrap(MethodInfo method)
{
var dm = new DynamicMethod(method.Name, typeof(object), new Type[] {
typeof(object), typeof(object[])
}, method.DeclaringType, true);
var il = dm.GetILGenerator();
if (!method.IsStatic)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Unbox_Any, method.DeclaringType);
}
var parameters = method.GetParameters();
for (int i = 0; i < parameters.Length; i++)
{
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldc_I4, i);
il.Emit(OpCodes.Ldelem_Ref);
il.Emit(OpCodes.Unbox_Any, parameters[i].ParameterType);
}
il.EmitCall(method.IsStatic || method.DeclaringType.IsValueType ?
OpCodes.Call : OpCodes.Callvirt, method, null);
if (method.ReturnType == null || method.ReturnType == typeof(void))
{
il.Emit(OpCodes.Ldnull);
}
else if (method.ReturnType.IsValueType)
{
il.Emit(OpCodes.Box, method.ReturnType);
}
il.Emit(OpCodes.Ret);
return (Func<object, object[], object>)dm.CreateDelegate(typeof(Func<object, object[], object>));
}
Koddan da görebileceğiniz gibi, yedi farklı yoldan basit bir işlemsiz yöntemi çağırmaya çalışıyorum:
- Doğrudan yöntem çağrısı
- kullanma
dynamic
- Yansıma yoluyla
- Çalışma
Action
zamanında önceden derlenmiş bir kullanım (bu nedenle sonuçlardan derleme süresi hariç).
- Bir kullanma
Action
(böylece derleme zamanında dahil) olmayan bir iş parçacığı güvenli Tembel değişkenini kullanarak, ihtiyaç duyulan ilk kez derlenmiş olduğu
- Testten önce oluşturulan dinamik olarak oluşturulmuş bir yöntemi kullanma.
- Test sırasında tembel olarak örneklenen dinamik olarak oluşturulmuş bir yöntem kullanma.
Her biri basit bir döngüde 1 milyon kez çağrılır. İşte zamanlama sonuçları:
Doğrudan: 3.4248ms
Dinamik: 45.0728ms
Yansıma: 888.4011ms
Önceden
Derlenmiş: 21.9166ms
Tembel Derlenmiş: 30.2045ms ILEanned: 8.4918ms
Tembelleştirilmiş: 14.3483ms
Bu nedenle, dynamic
anahtar kelimeyi kullanmak, yöntemi doğrudan çağırmaktan daha uzun bir sıra alırken, işlemi yaklaşık 50 milisaniyede bir milyon kez tamamlamayı başarır ve bu da onu yansımadan çok daha hızlı hale getirir. Dediğimiz yöntem, birkaç dizeyi bir araya getirmek veya bir koleksiyonda bir değer aramak gibi yoğun bir şey yapmaya çalışıyor olsaydı, bu işlemler büyük olasılıkla doğrudan arama ile arama arasındaki farktan çok daha ağır basacaktır dynamic
.
Performans, dynamic
gereksiz yere kullanmamak için birçok iyi nedenden sadece biridir , ancak gerçek dynamic
verilerle uğraşırken , dezavantajlardan çok daha ağır basan avantajlar sağlayabilir.
Güncelleme 4
Johnbot'un yorumuna dayanarak, Yansıma alanını dört ayrı teste böldüm:
new TimedAction("Reflection, find method", () =>
{
typeof(Foo).GetMethod("DoSomething").Invoke(foo, args);
}),
new TimedAction("Reflection, predetermined method", () =>
{
method.Invoke(foo, args);
}),
new TimedAction("Reflection, create a delegate", () =>
{
((Action)method.CreateDelegate(typeof(Action), foo)).Invoke();
}),
new TimedAction("Reflection, cached delegate", () =>
{
methodDelegate.Invoke();
}),
... ve işte karşılaştırma sonuçları:
Bu nedenle, çok fazla aramanız gereken belirli bir yöntemi önceden belirleyebilirseniz, bu yönteme atıfta bulunan önbelleğe alınmış bir temsilciyi çağırmak, yöntemin kendisini çağırmak kadar hızlıdır. Ancak, tam da onu çağırmak üzereyken hangi yöntemi arayacağınızı belirlemeniz gerekiyorsa, bunun için bir temsilci oluşturmak çok pahalıdır.