अद्यतन: जोड़ा गया precompiled और आलसी-संकलित मानक
अद्यतन 2: बाहर मुड़ता है, मैं गलत हूँ। एक संपूर्ण और सही उत्तर के लिए एरिक लिपर्ट की पोस्ट देखें। मैं बेंचमार्क नंबरों की खातिर इसे यहां छोड़ रहा हूं
* अपडेट 3: इस सवाल के मार्क ग्रेवेल के जवाब के आधार पर, IL-Emitted और Lazy IL-Emitted मानक जोड़े गए ।
मेरी जानकारी के लिए, dynamic
कीवर्ड का उपयोग रनटाइम के दौरान और स्वयं में किसी भी अतिरिक्त संकलन का कारण नहीं बनता है (हालांकि मुझे लगता है कि यह विशिष्ट परिस्थितियों में ऐसा कर सकता है, इस पर निर्भर करता है कि किस प्रकार की वस्तुएं आपके गतिशील चर का समर्थन कर रही हैं)।
प्रदर्शन के बारे में, dynamic
स्वाभाविक रूप से कुछ उपरि का परिचय होता है, लेकिन लगभग उतना नहीं जितना आप सोच सकते हैं। उदाहरण के लिए, मैंने सिर्फ एक बेंचमार्क चलाया जो इस तरह दिखता है:
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>));
}
जैसा कि आप कोड से देख सकते हैं, मैं एक सरल नो-ऑप विधि को सात अलग-अलग तरीकों से लागू करने का प्रयास करता हूं:
- डायरेक्ट मेथड कॉल
- का उपयोग करते हुए
dynamic
- प्रतिबिंब द्वारा
Action
उस का उपयोग करना जो रनटाइम के दौरान पूर्वनिर्धारित हो गया (इस प्रकार परिणामों से संकलन समय को छोड़कर)।
- एक का उपयोग करना
Action
(इस प्रकार संकलन समय सहित) है कि पहली बार यह आवश्यक है संकलित हो जाता है, एक गैर धागा सुरक्षित लेज़ी चर का उपयोग कर
- एक गतिशील रूप से उत्पन्न विधि का उपयोग करना जो परीक्षण से पहले बनाया जाता है।
- एक गतिशील रूप से उत्पन्न विधि का उपयोग करना जो परीक्षण के दौरान आलसी हो जाता है।
प्रत्येक को एक साधारण लूप में 1 मिलियन बार कहा जाता है। यहाँ समय के परिणाम हैं:
प्रत्यक्ष: 3.4248ms
गतिशील: 45.0728ms
चिंतन: 888.4011ms
Precompiled: 21.9166ms
LazyCompiled: 30.2045ms
ILEmitted: 8.4918ms
LazyILEmitted: 14.3483ms
इसलिए जब dynamic
कीवर्ड का उपयोग सीधे तरीके से कॉल करने की तुलना में अधिक समय तक होता है, तब भी यह ऑपरेशन को लगभग 50 मिलीसेकंड में एक लाख बार पूरा करने का प्रबंधन करता है, जिससे यह प्रतिबिंब की तुलना में बहुत तेज हो जाता है। यदि हम जिस विधि को कॉल करते हैं, वह कुछ गहन करने की कोशिश कर रही है, जैसे कुछ तारों को एक साथ जोड़ना या मूल्य के लिए एक संग्रह की खोज करना, उन ऑपरेशनों को प्रत्यक्ष कॉल और कॉल के बीच के अंतर को दूर करना होगा dynamic
।
प्रदर्शन dynamic
अनावश्यक रूप से उपयोग नहीं करने के कई अच्छे कारणों में से एक है , लेकिन जब आप वास्तव में dynamic
डेटा के साथ काम कर रहे होते हैं , तो यह उन लाभों को प्रदान कर सकता है जो नुकसान को दूर करते हैं।
अद्यतन ४
जॉनबॉट की टिप्पणी के आधार पर, मैंने परावर्तन क्षेत्र को चार अलग-अलग परीक्षणों में तोड़ दिया:
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();
}),
... और यहाँ बेंचमार्क परिणाम हैं:
इसलिए यदि आप एक विशिष्ट विधि को पूर्व निर्धारित कर सकते हैं, जिसे आपको कॉल करने की आवश्यकता होगी, तो उस विधि का संदर्भ देते हुए कैश्ड प्रतिनिधि को आमंत्रित करना उतना ही तेज़ होगा जितना कि विधि को कॉल करना। हालाँकि, अगर आपको यह निर्धारित करने की आवश्यकता है कि किस विधि को कॉल करना है जैसा कि आप इसे लागू करने वाले हैं, तो इसके लिए एक प्रतिनिधि बनाना बहुत महंगा है।