c# - अपवादों को फेंकने पर प्रदर्शन को चोट पहुंचाने/पकड़ने का प्रयास करें?




performance try-catch (7)

एक Microsoft कर्मचारी के साथ कोड समीक्षा के दौरान हम try{} ब्लॉक के अंदर कोड के एक बड़े खंड में आए। वह और एक आईटी प्रतिनिधि ने सुझाव दिया कि इसका कोड के प्रदर्शन पर प्रभाव हो सकता है। वास्तव में, उन्होंने सुझाव दिया कि अधिकांश कोड प्रयास / पकड़ ब्लॉक के बाहर होना चाहिए, और केवल महत्वपूर्ण वर्गों की जांच की जानी चाहिए। माइक्रोसॉफ्ट के कर्मचारी ने कहा और कहा कि आने वाले श्वेत पत्र गलत प्रयास / पकड़ ब्लॉक के खिलाफ चेतावनी देते हैं।

मैंने चारों ओर देखा है और पाया है कि यह अनुकूलन को प्रभावित कर सकता है , लेकिन ऐसा लगता है कि स्केल के बीच एक चर साझा किया जाता है।

मैं कोड की रखरखाव के बारे में नहीं पूछ रहा हूं, या सही अपवादों को संभालने के बारे में भी नहीं पूछ रहा हूं (प्रश्न में कोड को फिर से फैक्टरिंग की आवश्यकता है, इसमें कोई संदेह नहीं है)। मैं प्रवाह नियंत्रण के लिए अपवादों का उपयोग करने का भी जिक्र नहीं कर रहा हूं, यह ज्यादातर मामलों में स्पष्ट रूप से गलत है। वे महत्वपूर्ण मुद्दे हैं (कुछ अधिक महत्वपूर्ण हैं), लेकिन यहां पर ध्यान केंद्रित नहीं करते हैं।

अपवादों को फेंकने पर प्रदर्शन को प्रभावित करने / पकड़ने के प्रदर्शन को कैसे प्रभावित करते हैं?

संपादित करें: मैं एक बक्षीस जोड़ रहा हूँ। दिलचस्प प्रतिक्रियाएं हैं, लेकिन मैं कुछ और इनपुट प्राप्त करना चाहता हूं।


इसे जाँचे।

static public void Main(string[] args)
{
    Stopwatch w = new Stopwatch();
    double d = 0;

    w.Start();

    for (int i = 0; i < 10000000; i++)
    {
        try
        {
            d = Math.Sin(1);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }
    }

    w.Stop();
    Console.WriteLine(w.Elapsed);
    w.Reset();
    w.Start();

    for (int i = 0; i < 10000000; i++)
    {
        d = Math.Sin(1);
    }

    w.Stop();
    Console.WriteLine(w.Elapsed);
}

आउटपुट:

00:00:00.4269033  // with try/catch
00:00:00.4260383  // without.

मिलीसेकंड में:

449
416

नया कोड:

for (int j = 0; j < 10; j++)
{
    Stopwatch w = new Stopwatch();
    double d = 0;
    w.Start();

    for (int i = 0; i < 10000000; i++)
    {
        try
        {
            d = Math.Sin(d);
        }

        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }

        finally
        {
            d = Math.Sin(d);
        }
    }

    w.Stop();
    Console.Write("   try/catch/finally: ");
    Console.WriteLine(w.ElapsedMilliseconds);
    w.Reset();
    d = 0;
    w.Start();

    for (int i = 0; i < 10000000; i++)
    {
        d = Math.Sin(d);
        d = Math.Sin(d);
    }

    w.Stop();
    Console.Write("No try/catch/finally: ");
    Console.WriteLine(w.ElapsedMilliseconds);
    Console.WriteLine();
}

नए परिणाम:

   try/catch/finally: 382
No try/catch/finally: 332

   try/catch/finally: 375
No try/catch/finally: 332

   try/catch/finally: 376
No try/catch/finally: 333

   try/catch/finally: 375
No try/catch/finally: 330

   try/catch/finally: 373
No try/catch/finally: 329

   try/catch/finally: 373
No try/catch/finally: 330

   try/catch/finally: 373
No try/catch/finally: 352

   try/catch/finally: 374
No try/catch/finally: 331

   try/catch/finally: 380
No try/catch/finally: 329

   try/catch/finally: 374
No try/catch/finally: 334

कोशिश / पकड़ प्रदर्शन पर प्रभाव पड़ता है।

लेकिन यह एक बड़ा प्रभाव नहीं है। कोशिश करें / पकड़ जटिलता आम तौर पर ओ (1) है, बस एक साधारण असाइनमेंट की तरह, जब उन्हें लूप में रखा जाता है। तो आपको उन्हें बुद्धिमानी से उपयोग करना होगा।

Here प्रयास / पकड़ प्रदर्शन के बारे में एक संदर्भ है (हालांकि इसकी जटिलता की व्याख्या नहीं करता है, लेकिन यह निहित है)। फेंक अपवाद अनुभाग थ्रो पर एक नज़र डालें


पकड़ने के प्रयासों पर प्रदर्शन पर नगण्य प्रभाव पड़ता है लेकिन अपवाद फेंकना काफी बड़ा हो सकता है, शायद यह है कि आपका सहकर्मी उलझन में था।


प्रयास / पकड़ के बिना और कोशिश / पकड़ के बिना सभी आंकड़े देखने के बाद, जिज्ञासा ने मुझे दोनों मामलों के लिए जेनरेट करने के लिए पीछे देखने के लिए मजबूर किया। यहां कोड है:

सी#:

private static void TestWithoutTryCatch(){
    Console.WriteLine("SIN(1) = {0} - No Try/Catch", Math.Sin(1)); 
}

MSIL:

.method private hidebysig static void  TestWithoutTryCatch() cil managed
{
  // Code size       32 (0x20)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr      "SIN(1) = {0} - No Try/Catch"
  IL_0006:  ldc.r8     1.
  IL_000f:  call       float64 [mscorlib]System.Math::Sin(float64)
  IL_0014:  box        [mscorlib]System.Double
  IL_0019:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object)
  IL_001e:  nop
  IL_001f:  ret
} // end of method Program::TestWithoutTryCatch

सी#:

private static void TestWithTryCatch(){
    try{
        Console.WriteLine("SIN(1) = {0}", Math.Sin(1)); 
    }
    catch (Exception ex){
        Console.WriteLine(ex);
    }
}

MSIL:

.method private hidebysig static void  TestWithTryCatch() cil managed
{
  // Code size       49 (0x31)
  .maxstack  2
  .locals init ([0] class [mscorlib]System.Exception ex)
  IL_0000:  nop
  .try
  {
    IL_0001:  nop
    IL_0002:  ldstr      "SIN(1) = {0}"
    IL_0007:  ldc.r8     1.
    IL_0010:  call       float64 [mscorlib]System.Math::Sin(float64)
    IL_0015:  box        [mscorlib]System.Double
    IL_001a:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                  object)
    IL_001f:  nop
    IL_0020:  nop
    IL_0021:  leave.s    IL_002f //JUMP IF NO EXCEPTION
  }  // end .try
  catch [mscorlib]System.Exception 
  {
    IL_0023:  stloc.0
    IL_0024:  nop
    IL_0025:  ldloc.0
    IL_0026:  call       void [mscorlib]System.Console::WriteLine(object)
    IL_002b:  nop
    IL_002c:  nop
    IL_002d:  leave.s    IL_002f
  }  // end handler
  IL_002f:  nop
  IL_0030:  ret
} // end of method Program::TestWithTryCatch

मैं आईएल में एक विशेषज्ञ नहीं हूं लेकिन हम देख सकते हैं कि एक स्थानीय अपवाद वस्तु चौथी रेखा पर बनाई गई है। लोकल .locals init ([0] class [mscorlib]System.Exception ex) अपवाद .locals init ([0] class [mscorlib]System.Exception ex) उन चीज़ों के बाद विधि के बिना विधि / सत्रहवीं IL_0021: leave.s IL_002f लाइन तक IL_0021: leave.s IL_002f । यदि कोई अपवाद होता है तो नियंत्रण IL_0025: ldloc.0 पर कूदता है अन्यथा हम IL_002d: leave.s IL_002f लेबल करने के लिए IL_002d: leave.s IL_002f और फ़ंक्शन रिटर्न।

मैं सुरक्षित रूप से यह मान सकता हूं कि यदि कोई अपवाद नहीं होता है तो यह केवल अपवाद वस्तुओं को पकड़ने के लिए स्थानीय चर बनाने का ओवरहेड है और एक जंप निर्देश है।


मैंने कोशिश की वास्तविक प्रभाव का परीक्षण किया .. एक तंग पाश में try..catch , और किसी भी सामान्य स्थिति में प्रदर्शन चिंता होने के लिए यह बहुत छोटा है।

यदि लूप बहुत कम काम करता है (मेरे परीक्षण में मैंने एक x++ ), तो आप अपवाद हैंडलिंग के प्रभाव को माप सकते हैं। अपवाद हैंडलिंग के साथ लूप को चलाने के लिए लगभग दस गुना अधिक समय लगा।

यदि लूप कुछ वास्तविक काम करता है (मेरे परीक्षण में मैंने Int32.Parse विधि कहा जाता है), अपवाद हैंडलिंग का माप मापने के लिए बहुत कम प्रभाव पड़ता है। मुझे लूप के क्रम को स्वैप करके बहुत बड़ा अंतर मिला ...


सिद्धांत रूप में, एक कोशिश / पकड़ ब्लॉक कोड व्यवहार पर कोई प्रभाव नहीं पड़ेगा जब तक कि एक अपवाद वास्तव में नहीं होता है। कुछ दुर्लभ परिस्थितियां हैं, हालांकि, जहां कोशिश / पकड़ ब्लॉक का अस्तित्व का एक बड़ा प्रभाव हो सकता है, और कुछ असामान्य-लेकिन-शायद ही अस्पष्ट लोग जहां प्रभाव ध्यान देने योग्य हो सकता है। इसका कारण यह है कि दिए गए कोड जैसे:

Action q;
double thing1()
  { double total; for (int i=0; i<1000000; i++) total+=1.0/i; return total;}
double thing2()
  { q=null; return 1.0;}
...
x=thing1();     // statement1
x=thing2(x);    // statement2
doSomething(x); // statement3

संकलक इस तथ्य के आधार पर कथन 1 को अनुकूलित करने में सक्षम हो सकता है कि कथन 2 को कथन 3 से पहले निष्पादित करने की गारंटी है। यदि संकलक यह पहचान सकता है कि उस चीज़ 1 का कोई दुष्प्रभाव नहीं है और चीज़ 2 वास्तव में एक्स का उपयोग नहीं करता है, तो यह सुरक्षित रूप से चीज़ 1 को पूरी तरह से छोड़ सकता है। यदि [इस मामले में] चीज़ 1 महंगी थी, तो यह एक प्रमुख अनुकूलन हो सकता है, हालांकि जिन मामलों में चीज 1 महंगा है, वे भी हैं जिन्हें संकलक कम से कम अनुकूलित करने की संभावना रखते हैं। मान लीजिए कि कोड बदल दिया गया था:

x=thing1();      // statement1
try
{ x=thing2(x); } // statement2
catch { q(); }
doSomething(x);  // statement3

अब घटनाओं का एक अनुक्रम मौजूद है जहां कथन 3 निष्पादित किए बिना कथन 2 निष्पादित कर सकता है। यहां तक ​​कि अगर thing2 के लिए कोड में कुछ भी अपवाद नहीं फेंक सकता है, तो यह संभव होगा कि एक और थ्रेड thing2 कर सके। thing2 यह नोटिस करने के लिए कि q साफ़ कर दिया गया था और इसे Thread.ResetAbort सेट किया गया था। Thread.ResetAbort , और उसके बाद Thread.Abort() कथन 2 ने x को अपना मूल्य लिखा। फिर catch Thread.ResetAbort() निष्पादित करेगा। Thread.ResetAbort() [प्रतिनिधि q माध्यम से], निष्पादन को कथन 3 के साथ जारी रखने की इजाजत देता है। घटनाओं का ऐसा अनुक्रम निश्चित रूप से असाधारण रूप से असंभव होगा, लेकिन एक संकलक को कोड उत्पन्न करने की आवश्यकता होती है जो विनिर्देशों के अनुसार काम करता है, भले ही ऐसी असंभव घटनाएं होती हैं।

आम तौर पर, कंपाइलर जटिल लोगों की तुलना में कोड के साधारण बिट्स छोड़ने के अवसरों को नोटिस करने की अधिक संभावना है, और इस प्रकार अपवादों को कभी भी फेंकने पर प्रयास / प्रभाव को प्रभावित करने के लिए दुर्लभ होगा। फिर भी, ऐसी कुछ स्थितियां हैं जहां एक प्रयास / पकड़ ब्लॉक का अस्तित्व अनुकूलन को रोक सकता है - लेकिन कोशिश / पकड़ने के लिए - कोड को तेजी से चलाने की अनुमति होगी।


बेन एम के उदाहरण में संरचना अलग है। इसे लूप के अंदर के अंदर ओवरहेड बढ़ा दिया जाएगा जिससे यह दोनों मामलों के बीच अच्छी तुलना नहीं होगी।

निम्नलिखित तुलना के लिए अधिक सटीक है जहां संपूर्ण कोड जांचने के लिए (परिवर्तनीय घोषणा सहित) कोशिश / कैच ब्लॉक के अंदर है:

        for (int j = 0; j < 10; j++)
        {
            Stopwatch w = new Stopwatch();
            w.Start();
            try { 
                double d1 = 0; 
                for (int i = 0; i < 10000000; i++) { 
                    d1 = Math.Sin(d1);
                    d1 = Math.Sin(d1); 
                } 
            }
            catch (Exception ex) {
                Console.WriteLine(ex.ToString()); 
            }
            finally { 
                //d1 = Math.Sin(d1); 
            }
            w.Stop(); 
            Console.Write("   try/catch/finally: "); 
            Console.WriteLine(w.ElapsedMilliseconds); 
            w.Reset(); 
            w.Start(); 
            double d2 = 0; 
            for (int i = 0; i < 10000000; i++) { 
                d2 = Math.Sin(d2);
                d2 = Math.Sin(d2); 
            } 
            w.Stop(); 
            Console.Write("No try/catch/finally: "); 
            Console.WriteLine(w.ElapsedMilliseconds); 
            Console.WriteLine();
        }

जब मैंने बेन एम से मूल परीक्षण कोड चलाया, तो मैंने डीबग और रेलीस कॉन्फ़िगरेशन दोनों में एक अंतर देखा।

इस संस्करण में, मैंने डीबग संस्करण (वास्तव में अन्य संस्करण से अधिक) में एक अंतर देखा, लेकिन रिलीज संस्करण में इसका कोई फर्क नहीं पड़ता।

सम्मेलन :
इन परीक्षणों के आधार पर, मुझे लगता है कि हम कह सकते हैं कि प्रयास / कैच प्रदर्शन पर थोड़ा प्रभाव डालता है।

संपादित करें:
मैंने लूप वैल्यू को 10000000 से 1000000000 तक बढ़ाने की कोशिश की, और रिलीज में कुछ अंतर पाने के लिए रिलीज में फिर से भाग गया, और परिणाम यह था:

   try/catch/finally: 509
No try/catch/finally: 486

   try/catch/finally: 479
No try/catch/finally: 511

   try/catch/finally: 475
No try/catch/finally: 477

   try/catch/finally: 477
No try/catch/finally: 475

   try/catch/finally: 475
No try/catch/finally: 476

   try/catch/finally: 477
No try/catch/finally: 474

   try/catch/finally: 475
No try/catch/finally: 475

   try/catch/finally: 476
No try/catch/finally: 476

   try/catch/finally: 475
No try/catch/finally: 476

   try/catch/finally: 475
No try/catch/finally: 474

आप देखते हैं कि परिणाम असुविधाजनक है। कुछ मामलों में Try / Catch का उपयोग करने वाला संस्करण वास्तव में तेज़ है!





try-catch