ลอง / บล็อกจับเจ็บประสิทธิภาพเมื่อยกเว้นจะไม่โยน?


274

ในระหว่างการตรวจสอบรหัสกับพนักงานของ Microsoft เราพบเจอโค้ดส่วนใหญ่ภายในtry{}บล็อก เธอและตัวแทนด้านไอทีแนะนำว่าสิ่งนี้อาจมีผลต่อประสิทธิภาพของรหัส ในความเป็นจริงพวกเขาแนะนำว่ารหัสส่วนใหญ่ควรอยู่นอกบล็อคลอง / จับและควรตรวจสอบเฉพาะส่วนที่สำคัญเท่านั้น พนักงานไมโครซอฟท์ได้เพิ่มและกล่าวว่าเอกสารทางเทคนิคที่จะเกิดขึ้นเตือนว่าบล็อก try / catch ไม่ถูกต้อง

ฉันได้ดูไปรอบ ๆ และพบว่าสามารถส่งผลกระทบต่อการปรับแต่งได้ แต่ดูเหมือนว่าจะใช้เฉพาะเมื่อมีการแชร์ตัวแปรระหว่างขอบเขต

ฉันไม่ได้ถามเกี่ยวกับความสามารถในการบำรุงรักษาของรหัสหรือแม้กระทั่งการจัดการข้อยกเว้นที่ถูกต้อง (รหัสที่เป็นปัญหาต้องได้รับการพิจารณาใหม่ ฉันยังไม่ได้หมายถึงการใช้ข้อยกเว้นสำหรับการควบคุมการไหลนี่เป็นความผิดที่ชัดเจนในกรณีส่วนใหญ่ สิ่งเหล่านี้เป็นปัญหาที่สำคัญ (บางอย่างมีความสำคัญมากกว่า) แต่ไม่เน้นที่นี่

บล็อก try / catch มีผลกับประสิทธิภาพอย่างไรเมื่อข้อยกเว้นไม่ถูกส่งออก?


147
"ผู้ที่จะเสียสละความถูกต้องในการแสดงไม่สมควรได้รับ"
Joel Coehoorn

16
ที่กล่าวว่าความถูกต้องไม่จำเป็นต้องเสียสละเพื่อประสิทธิภาพ
Dan Davies Brackett

19
อยากรู้อยากเห็นง่ายๆเกี่ยวกับวิธีการที่?
Samantha Branham

63
@ Joel: บางที Kobi แค่อยากรู้คำตอบจากความอยากรู้ การรู้ว่าการแสดงจะดีขึ้นหรือแย่ลงไม่ได้แปลว่าเขาจะทำทุกอย่างที่บ้าด้วยรหัสของเขา การแสวงหาความรู้ไม่ใช่เพื่อประโยชน์ของตัวเองใช่ไหม
ลุ

6
นี่เป็นอัลกอริทึมที่ดีสำหรับการรู้ว่าจะทำการเปลี่ยนแปลงนี้หรือไม่ ก่อนกำหนดเป้าหมายประสิทธิภาพตามลูกค้าที่มีความหมาย ประการที่สองเขียนรหัสให้ถูกต้องและชัดเจนก่อน ประการที่สามทดสอบกับเป้าหมายของคุณ ข้อที่สี่ถ้าคุณบรรลุเป้าหมายของคุณให้รีบออกไปทำงานเร็วและไปที่ชายหาด ประการที่ห้าหากคุณไม่บรรลุเป้าหมายของคุณให้ใช้ผู้สร้างโปรไฟล์เพื่อค้นหารหัสที่ช้าเกินไป หกถ้ารหัสนั้นเกิดช้าเกินไปเนื่องจากตัวจัดการข้อยกเว้นที่ไม่จำเป็นให้ถอดตัวจัดการข้อยกเว้นออกเท่านั้น ถ้าไม่แก้ไขรหัสที่ช้าเกินไป จากนั้นย้อนกลับไปยังขั้นตอนที่สาม
Eric Lippert

คำตอบ:


203

ตรวจสอบ.

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

24
คุณสามารถลองพวกเขาในลำดับย้อนกลับได้เช่นกันเพื่อให้แน่ใจว่าการรวบรวม JIT ไม่มีผลกับอดีตหรือไม่
JoshJordan

28
โปรแกรมเช่นนี้ดูเหมือนจะเป็นตัวเลือกที่ดีสำหรับการทดสอบผลกระทบของการจัดการข้อยกเว้นสิ่งที่เกิดขึ้นในการลอง {} catch {} บล็อกแบบปกติจะได้รับการปรับให้เหมาะสม ผมอาจจะออกไปรับประทานอาหารกลางวันในที่ ...
LorenVS

30
นี่คือการสร้างการดีบัก JIT ไม่ปรับให้เหมาะสม
Ben M

7
นี่ไม่จริงเลยลองคิดดู คุณใช้ลองกี่ครั้งในการวนซ้ำ? เวลาส่วนใหญ่คุณจะใช้วนรอบใน try.c
Athiwat Chunlakhan

9
จริงๆ? "บล็อกลอง / catch มีผลกับประสิทธิภาพอย่างไรเมื่อข้อยกเว้นไม่ถูกส่งออกไป"
Ben M

105

หลังจากที่ได้เห็นสถิติทั้งหมดสำหรับการลอง / จับและไม่ต้องลอง / จับความอยากรู้ทำให้ฉันต้องมองด้านหลังเพื่อดูว่าอะไรถูกสร้างขึ้นสำหรับทั้งสองกรณี นี่คือรหัส:

ค#:

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

ฉันไม่ได้เป็นผู้เชี่ยวชาญใน IL แต่เราจะเห็นได้ว่าวัตถุยกเว้นท้องถิ่นถูกสร้างขึ้นบนบรรทัดที่สี่.locals init ([0] class [mscorlib]System.Exception ex)หลังจากสิ่งที่มีความสวยเช่นเดียวกับวิธีการได้โดยไม่ต้องลอง / IL_0021: leave.s IL_002fจับจนเส้นสิบเจ็ด หากมีข้อผิดพลาดเกิดขึ้นการควบคุมจะข้ามไปที่บรรทัดIL_0025: ldloc.0มิฉะนั้นเราจะข้ามไปที่ป้ายชื่อIL_002d: leave.s IL_002fและฟังก์ชั่นกลับมา

ฉันสามารถสันนิษฐานได้อย่างปลอดภัยว่าหากไม่มีข้อยกเว้นเกิดขึ้นมันเป็นค่าใช้จ่ายในการสร้างตัวแปรท้องถิ่นเพื่อเก็บวัตถุยกเว้นเท่านั้นและคำสั่งการกระโดด


33
เอาล่ะ IL มีการลอง / จับบล็อกในรูปแบบเดียวกับใน C # ดังนั้นนี่จึงไม่ได้แสดงให้เห็นว่าการลอง / จับค่าใช้จ่ายมีความหมายมากแค่ไหนในเบื้องหลัง! เพียงแค่ว่า IL ไม่ได้เพิ่มมากขึ้นไม่ได้หมายความว่ามันไม่ได้เพิ่มอะไรบางอย่างในรหัสประกอบที่รวบรวม IL เป็นเพียงตัวแทนของภาษา. NET ทั้งหมดเท่านั้น ไม่ใช่รหัสเครื่อง!
กลัว

64

ไม่หากการปรับให้เหมาะสมเล็กน้อยลอง / สุดท้ายบล็อกติ๊ดจริงมีผลกระทบที่วัดได้ในโปรแกรมของคุณคุณอาจไม่ควรใช้. NET ในครั้งแรก


10
นั่นคือจุดที่ยอดเยี่ยม - เปรียบเทียบกับรายการอื่น ๆ ในรายการของเราอันนี้ควรจะจิ๋ว เราควรเชื่อใจในคุณสมบัติภาษาพื้นฐานที่จะทำงานอย่างถูกต้องและเพิ่มประสิทธิภาพสิ่งที่เราสามารถควบคุมได้ (sql, ดัชนี, อัลกอริทึม)
Kobi

3
คิดว่าคู่ลูปแน่น ตัวอย่างลูปที่คุณอ่านและยกเลิกการทำให้เป็นออบเจ็กต์จากสตรีมข้อมูลซ็อกเก็ตในเซิร์ฟเวอร์เกมและคุณพยายามบีบให้มากที่สุด ดังนั้นคุณ MessagePack สำหรับการทำให้เป็นอันดับวัตถุแทนที่จะเป็นรูปแบบไบนารีและใช้ ArrayPool <byte> แทนที่จะเพียงแค่สร้างอาร์เรย์ไบต์ ฯลฯ ... ในสถานการณ์เหล่านี้ผลกระทบของหลาย ๆ (อาจซ้อนกัน) ลองจับบล็อกภายในลูปคับ การปรับให้เหมาะสมบางอย่างจะถูกข้ามโดยคอมไพเลอร์และตัวแปรข้อยกเว้นจะไปที่ Gen0 GC ทั้งหมดที่ฉันพูดคือมีบางสถานการณ์ที่ทุกอย่างมีผลกระทบ
tcwicks

35

คำอธิบายที่ค่อนข้างครอบคลุมของ. NET exception model

เกร็ดความรู้ในการปฏิบัติงานของ Rico Mariani: ค่าใช้จ่ายการยกเว้น: เมื่อใดที่จะโยนและเมื่อไร

ค่าใช้จ่ายชนิดแรกคือค่าใช้จ่ายคงที่ที่มีข้อยกเว้นในการจัดการโค้ดของคุณเลย ข้อยกเว้นที่ได้รับการจัดการจริง ๆ แล้วค่อนข้างดีที่นี่โดยที่ฉันหมายความว่าต้นทุนคงที่สามารถต่ำกว่าพูดใน C ++ ทำไมนี้ ค่าใช้จ่ายคงที่เกิดขึ้นจริงในสองประเภท: ประการแรกไซต์จริงของลอง / สุดท้าย / จับ / โยนโดยที่มีรหัสสำหรับสิ่งก่อสร้างเหล่านั้น ประการที่สองในรหัสที่ไม่มีการเปลี่ยนแปลงมีค่าใช้จ่ายในการลักลอบที่เกี่ยวข้องกับการติดตามวัตถุทั้งหมดที่จะต้องถูกทำลายในกรณีที่มีข้อยกเว้นจะถูกโยน มีจำนวนของตรรกะการล้างข้อมูลจำนวนมากที่ต้องมีอยู่และส่วนที่ลับ ๆ ล่อๆคือแม้แต่รหัสที่ไม่ได้ '

Dmitriy Zaslavskiy:

ตามหมายเหตุของ Chris Brumme: นอกจากนี้ยังมีค่าใช้จ่ายที่เกี่ยวข้องกับความจริงแล้วการเพิ่มประสิทธิภาพบางอย่างไม่ได้ถูกดำเนินการโดย JIT เมื่อมีการจับ


1
สิ่งที่เกี่ยวกับ C ++ ก็คือคลังข้อมูลขนาดใหญ่มากจะมีข้อยกเว้น ไม่มีทางเลือกเกี่ยวกับพวกเขา คุณต้องออกแบบวัตถุของคุณด้วยนโยบายการยกเว้นบางอย่างและเมื่อคุณทำเสร็จแล้วจะไม่มีค่าใช้จ่ายใด ๆ
David Thornley

คำเรียกร้องของ Rico Mariani นั้นผิดอย่างสิ้นเชิงกับภาษา C ++ "ค่าใช้จ่ายคงที่อาจต่ำกว่าที่พูดใน C ++" - นี่ไม่จริง แม้ว่าฉันไม่แน่ใจว่าสิ่งที่เป็นกลไกการยกเว้นการออกแบบในปี 2003 เมื่อเขียนบทความ C ++ ไม่มีค่าใช้จ่ายเลยเมื่อไม่มีการโยนข้อยกเว้นไม่ว่าคุณจะมีบล็อก try / catch เท่าไรและอยู่ที่ไหนก็ตาม
BJovke

1
@BJovke C ++ "การจัดการข้อยกเว้นศูนย์ต้นทุน" เท่านั้นหมายความว่าไม่มีค่าใช้จ่ายในการดำเนินการเมื่อข้อยกเว้นไม่ได้ถูกโยนทิ้ง แต่ยังคงมีค่าใช้จ่ายขนาดใหญ่ของรหัสเนื่องจากการล้างข้อมูลโค้ดที่เรียกว่า destructors ทั้งหมดบนข้อยกเว้น นอกจากนี้ในขณะที่ไม่มีรหัสข้อยกเว้นเฉพาะที่ถูกสร้างขึ้นบนเส้นทางรหัสปกติค่าใช้จ่ายยังไม่เป็นศูนย์เพราะความเป็นไปได้ของข้อยกเว้นยัง จำกัด เครื่องมือเพิ่มประสิทธิภาพ (เช่นสิ่งที่จำเป็นในกรณีที่มีข้อยกเว้นจำเป็นต้องอยู่ รอบ ๆ ที่ -> ค่าสามารถละทิ้งได้น้อยลง -> การจัดสรรการลงทะเบียนที่มีประสิทธิภาพน้อยกว่า)
Daniel

24

โครงสร้างที่แตกต่างกันในตัวอย่างจากเบน M มันจะขยายค่าใช้จ่ายในforวงด้านในที่จะทำให้มันไม่ได้เป็นการเปรียบเทียบที่ดีระหว่างทั้งสองกรณี

ต่อไปนี้มีความแม่นยำมากขึ้นสำหรับการเปรียบเทียบที่รหัสทั้งหมดเพื่อตรวจสอบ (รวมถึงการประกาศตัวแปร) อยู่ในบล็อกลอง / จับ

        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();
        }

เมื่อฉันรันรหัสทดสอบดั้งเดิมจากBen Mฉันสังเกตเห็นความแตกต่างทั้งในการกำหนดค่า Debug และ Releas

รุ่นนี้ฉันสังเกตเห็นความแตกต่างในเวอร์ชันการดีบัก (จริง ๆ แล้วมากกว่าเวอร์ชั่นอื่น ๆ ) แต่มันก็ไม่ต่างกันในเวอร์ชั่น Release

Conclution :
จากการทดสอบเหล่านี้ผมคิดว่าเราสามารถพูดได้ว่าลอง / จับไม่ได้มีผลกระทบต่อประสิทธิภาพการทำงานเล็ก ๆ

แก้ไข:
ฉันพยายามเพิ่มค่าลูปจาก 10,000,000 เป็น 1000000000 และวิ่งอีกครั้งใน Release เพื่อรับความแตกต่างบางอย่างในรีลีสและผลลัพธ์คือ:

   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 เร็วกว่าจริง!


1
ฉันก็สังเกตเห็นเช่นนี้บางครั้งมันก็เร็วขึ้นด้วยลอง / จับ ฉันแสดงความคิดเห็นในคำตอบของเบ็น อย่างไรก็ตามไม่เหมือนกับผู้ลงคะแนน 24 คนฉันไม่ชอบการเปรียบเทียบประเภทนี้ฉันไม่คิดว่ามันเป็นตัวบ่งชี้ที่ดี รหัสนี้เร็วขึ้นในกรณีนี้ แต่จะเป็นเสมอหรือไม่
Kobi

5
สิ่งนี้ไม่ได้พิสูจน์ว่าเครื่องของคุณกำลังทำงานอื่น ๆ หลายอย่างพร้อมกันหรือไม่? เวลาที่ผ่านไปไม่ได้เป็นการวัดที่ดีคุณต้องใช้ตัวสร้างโปรไฟล์ที่บันทึกเวลาตัวประมวลผลไม่ใช่เวลาที่ผ่านไป
Colin Desmond

2
@Kobi: ฉันเห็นด้วยว่านี่ไม่ใช่วิธีที่ดีที่สุดในการเปรียบเทียบหากคุณกำลังจะเผยแพร่มันเป็นข้อพิสูจน์ว่าโปรแกรมของคุณทำงานได้เร็วกว่าสิ่งอื่นใดหรือบางอย่าง แต่สามารถให้คุณเป็นนักพัฒนาที่บ่งชี้ว่า . ในกรณีนี้ฉันคิดว่าเราสามารถพูดได้ว่าความแตกต่าง (อย่างน้อยสำหรับการกำหนดค่าการวางจำหน่าย) นั้นเป็นสิ่งที่ละเลย
กลัว

1
คุณไม่ใช่เวลาtry/catchที่นี่ คุณจับเวลา 12 ครั้ง / ลองจับส่วนที่สำคัญกับ 10M loops เสียงรบกวนของลูปจะกำจัดอิทธิพลใด ๆ ที่ลอง / จับได้ ถ้าคุณใส่ try / catch เข้าไปใน loop tight แทนและเปรียบเทียบกับ / ไม่คุณจะต้องเสียลอง try / catch แทน (ไม่ต้องสงสัยเลยว่าการเขียนโค้ดดังกล่าวไม่ใช่วิธีปฏิบัติที่ดีโดยทั่วไป แต่ถ้าคุณต้องการกำหนดเวลาค่าใช้จ่ายในการก่อสร้างนั่นเป็นวิธีที่คุณทำ) ทุกวันนี้ BenchmarkDotNet เป็นเครื่องมือสำหรับการกำหนดเวลาดำเนินการที่เชื่อถือได้
Abel

15

ฉันทดสอบผลกระทบที่เกิดขึ้นจริงtry..catchในวงแคบและมันเล็กเกินไปที่จะกังวลเกี่ยวกับประสิทธิภาพในสถานการณ์ปกติ

หากลูปทำงานน้อยมาก (ในการทดสอบของฉันที่ฉันทำx++) คุณสามารถวัดผลกระทบของการจัดการข้อยกเว้น การวนซ้ำที่มีการจัดการข้อยกเว้นใช้เวลาทำงานนานกว่าสิบเท่า

หากลูปทำงานจริงบางอย่าง (ในการทดสอบของฉันฉันเรียกว่าวิธี Int32.Parse) การจัดการข้อยกเว้นมีผลกระทบน้อยเกินไปที่จะวัดได้ ฉันมีความแตกต่างที่ใหญ่กว่ามากโดยการเปลี่ยนลำดับของลูป ...


11

ลองบล็อก catch มีผลกระทบเล็กน้อยต่อประสิทธิภาพ แต่ข้อยกเว้นการขว้างปาค่อนข้างใหญ่นี่อาจเป็นที่ที่เพื่อนร่วมงานของคุณสับสน


8

ความพยายาม / จับ HAS ส่งผลกระทบต่อประสิทธิภาพ

แต่มันไม่ได้เป็นผลกระทบมาก ความซับซ้อนของลอง / จับมักจะเป็น O (1) เช่นเดียวกับการมอบหมายอย่างง่ายยกเว้นเมื่อพวกเขาอยู่ในลูป ดังนั้นคุณต้องใช้มันอย่างชาญฉลาด

นี่คือข้อมูลอ้างอิงเกี่ยวกับประสิทธิภาพการทำงานแบบลอง / จับ (ไม่ได้อธิบายความซับซ้อนของมัน แต่มันบอกเป็นนัย) ลองดูที่โยนลดจำนวนข้อยกเว้นส่วน


3
ความซับซ้อนคือ O (1) มันไม่ได้มีความหมายมากเกินไป ตัวอย่างเช่นหากคุณติดตั้งส่วนของรหัสที่เรียกว่าบ่อยครั้งมากด้วย try-catch (หรือคุณพูดถึงการวนซ้ำ) O (1) s สามารถเพิ่มจำนวนที่วัดได้ในตอนท้าย
Csaba Toth

6

ในทางทฤษฎีบล็อกแบบลอง / จับจะไม่มีผลกับพฤติกรรมของโค้ดเว้นแต่จะมีข้อยกเว้นเกิดขึ้นจริง อย่างไรก็ตามมีสถานการณ์ที่หายากบางอย่างที่การมีอยู่ของบล็อก try / catch อาจมีผลกระทบที่สำคัญและบางกรณีที่ผิดปกติ แต่ยากที่จะปิดบังซึ่งผลกระทบสามารถสังเกตได้ เหตุผลนี้เป็นรหัสที่ได้รับเช่น:

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 ตามความจริงที่ว่า statement2 รับประกันว่าจะดำเนินการก่อนคำสั่ง 3 หากคอมไพเลอร์สามารถรับรู้ว่าสิ่งที่ 1 ไม่มีผลข้างเคียงและสิ่งที่ 2 ไม่ได้ใช้จริง ๆ x ก็อาจละเว้นสิ่งที่ 1 อย่างสิ้นเชิง หาก [ในกรณีนี้] สิ่งที่ 1 มีราคาแพงนั่นอาจเป็นการเพิ่มประสิทธิภาพที่สำคัญแม้ว่ากรณีที่สิ่งที่ 1 มีราคาแพงก็เป็นสิ่งที่คอมไพเลอร์น่าจะปรับให้เหมาะสมอย่างน้อยที่สุด สมมติว่ารหัสมีการเปลี่ยนแปลง:

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

ขณะนี้มีลำดับเหตุการณ์ที่ซึ่ง statement3 สามารถดำเนินการได้โดยที่ไม่มี statement2 กำลังดำเนินการ แม้ว่าไม่มีอะไรในรหัสสำหรับthing2จะโยนข้อยกเว้นว่ามันจะเป็นไปได้ว่าหัวข้ออื่นสามารถใช้Interlocked.CompareExchangeเพื่อแจ้งให้ทราบว่าqได้รับการล้างและตั้งค่าให้Thread.ResetAbortแล้วดำเนินการThread.Abort()ก่อนที่จะเขียน statement2 xค่าในการ จากนั้นcatchจะดำเนินการThread.ResetAbort()[ผ่านตัวแทนq] อนุญาตให้ดำเนินการต่อด้วยคำสั่ง 3 แน่นอนว่าลำดับเหตุการณ์จะไม่น่าเป็นไปได้อย่างยอดเยี่ยม แต่คอมไพเลอร์จำเป็นต้องสร้างรหัสที่ทำงานตามข้อกำหนดแม้เมื่อเหตุการณ์ที่ไม่น่าจะเกิดขึ้นดังกล่าวเกิดขึ้น

โดยทั่วไปคอมไพเลอร์มีแนวโน้มที่จะสังเกตเห็นโอกาสที่จะทิ้งโค้ดง่าย ๆ มากกว่าโค้ดที่ซับซ้อนและดังนั้นจึงเป็นเรื่องยากสำหรับการลอง / จับอาจส่งผลกระทบต่อประสิทธิภาพการทำงานถ้าข้อยกเว้นไม่เคยถูกโยนทิ้ง ถึงกระนั้นมีบางสถานการณ์ที่การมีอยู่ของบล็อก try / catch อาจป้องกันการปรับให้เหมาะสมซึ่ง - แต่สำหรับ try / catch - จะทำให้โค้ดทำงานได้เร็วขึ้น


5

แม้ว่า " การป้องกันดีกว่าการจัดการ " ในมุมมองของประสิทธิภาพและประสิทธิภาพเราสามารถเลือกลองการตรวจสอบล่วงหน้า พิจารณารหัสด้านล่าง:

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 1; i < int.MaxValue; i++)
{
    if (i != 0)
    {
        int k = 10 / i;
    }
}
stopwatch.Stop();
Console.WriteLine($"With Checking: {stopwatch.ElapsedMilliseconds}");
stopwatch.Reset();
stopwatch.Start();
for (int i = 1; i < int.MaxValue; i++)
{
    try
    {
        int k = 10 / i;
    }
    catch (Exception)
    {

    }
}
stopwatch.Stop();
Console.WriteLine($"With Exception: {stopwatch.ElapsedMilliseconds}");

นี่คือผลลัพธ์:

With Checking: 20367
With Exception: 13998

4

ดูการอภิปรายเกี่ยวกับการนำไปใช้แบบลอง / จับสำหรับการอภิปรายว่าการทำงานแบบลอง / จับบล็อกอย่างไรและการใช้งานบางอย่างมีค่าใช้จ่ายสูงและบางส่วนมีค่าใช้จ่ายเป็นศูนย์เมื่อไม่มีข้อยกเว้นเกิดขึ้น โดยเฉพาะอย่างยิ่งฉันคิดว่าการใช้งาน Windows 32 บิตมีค่าใช้จ่ายสูงและการใช้งาน 64 บิตไม่มี


สิ่งที่ฉันอธิบายมีสองวิธีที่แตกต่างกันในการใช้ข้อยกเว้น วิธีการที่นำมาใช้อย่างเท่าเทียมกันกับ C ++ และ C # รวมถึงรหัสที่มีการจัดการ / ไม่มีการจัดการ MS ตัวไหนที่เลือกสำหรับ C # ของพวกเขาที่ฉันไม่รู้ แต่สถาปัตยกรรมการจัดการข้อยกเว้นของแอปพลิเคชันระดับเครื่องที่ MS เตรียมไว้ใช้รูปแบบที่เร็วกว่า ฉันจะแปลกใจเล็กน้อยหากการใช้งาน C # สำหรับ 64 บิตไม่ได้ใช้งาน
Ira Baxter
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.