ผลของข้อยกเว้นต่อประสิทธิภาพใน Java คืออะไร


496

คำถาม: การจัดการข้อยกเว้นใน Java ช้าจริงหรือไม่

ภูมิปัญญาดั้งเดิมรวมถึงผลการค้นหาของ Google จำนวนมากกล่าวว่าไม่ควรใช้ตรรกะพิเศษสำหรับการไหลเวียนของโปรแกรมปกติใน Java โดยปกติจะมีสองเหตุผล

  1. มันช้ามาก - แม้ลำดับความสำคัญจะช้ากว่ารหัสปกติ (เหตุผลที่ให้แตกต่างกันไป)

และ

  1. มันยุ่งเพราะคนคาดหวังว่าข้อผิดพลาดเท่านั้นที่จะจัดการในรหัสพิเศษ

คำถามนี้เกี่ยวกับ # 1

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

การทดสอบของฉัน (โดยใช้ Java 1.6.0_07, Java HotSpot 10.0, บน 32 บิต Linux) ระบุว่าการจัดการข้อยกเว้นไม่ช้ากว่าโค้ดปกติ ฉันลองใช้วิธีการในวงที่รันรหัสบางอย่าง ในตอนท้ายของวิธีการที่ผมใช้บูลที่จะบ่งชี้ว่าจะกลับมาหรือโยน วิธีนี้การประมวลผลจริงจะเหมือนกัน ฉันลองใช้วิธีการตามคำสั่งต่าง ๆ และหาค่าเฉลี่ยเวลาทดสอบของฉันโดยคิดว่า JVM อาจร้อนขึ้น ในการทดสอบทั้งหมดของฉันการขว้างอย่างน้อยเท่ากับการส่งคืนถ้าไม่เร็วขึ้น (เร็วกว่า 3.1%) ฉันเปิดกว้างสำหรับความเป็นไปได้ที่การทดสอบของฉันผิด แต่ฉันไม่ได้เห็นอะไรเลยเกี่ยวกับตัวอย่างโค้ดการเปรียบเทียบการทดสอบหรือผลลัพธ์ในปีที่แล้วหรือสองปีที่แสดงการจัดการข้อยกเว้นใน Java จริงๆ ช้า.

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

ในการจัดการข้อยกเว้นกระดาษอย่างมีประสิทธิภาพ Java ในการรวบรวมแบบทันเวลาผู้เขียนแนะนำว่าการมีตัวจัดการข้อยกเว้นเพียงอย่างเดียวแม้ว่าจะไม่มีข้อยกเว้นถูกโยนทิ้งก็เพียงพอที่จะป้องกันไม่ให้คอมไพเลอร์ JIT ทำการปรับรหัสให้เหมาะสม . ฉันยังไม่ได้ทดสอบทฤษฎีนี้


8
ฉันรู้ว่าคุณไม่ได้ถามเกี่ยวกับ 2) แต่คุณควรตระหนักว่าการใช้ข้อยกเว้นสำหรับโฟลว์ของโปรแกรมนั้นไม่ดีไปกว่าการใช้ GOTO บางคนปกป้อง gotos บางคนจะปกป้องสิ่งที่คุณกำลังพูดถึง แต่ถ้าคุณถามคนที่ใช้งานและบำรุงรักษาเป็นระยะเวลาหนึ่งพวกเขาจะบอกคุณว่าทั้งคู่ยากที่จะรักษาแนวปฏิบัติในการออกแบบ (และอาจจะสาปแช่ง ชื่อของบุคคลที่คิดว่าฉลาดพอที่จะตัดสินใจใช้)
Bill K

80
Bill ซึ่งอ้างว่าการใช้ข้อยกเว้นสำหรับโฟลว์ของโปรแกรมนั้นไม่ดีไปกว่าการใช้ GOTO นั้นไม่ดีไปกว่าการอ้างว่าการใช้เงื่อนไขและลูปสำหรับโฟลว์ของโปรแกรมนั้นไม่ดีไปกว่าการใช้ GOTO มันเป็นปลาเฮอริ่งแดง อธิบายเกี่ยวกับตัวคุณ. ข้อยกเว้นสามารถและใช้อย่างมีประสิทธิภาพสำหรับการไหลของโปรแกรมในภาษาอื่น ๆ รหัส Idiomatic Python ใช้ข้อยกเว้นเป็นประจำเช่น ฉันสามารถและมีการบำรุงรักษารหัสที่ใช้ข้อยกเว้นในลักษณะนี้ (ไม่ใช่ Java) และฉันไม่คิดว่าจะมีอะไรผิดปกติกับมัน
mmalone

14
@mmalone โดยใช้ข้อยกเว้นสำหรับโฟลว์การควบคุมปกติเป็นความคิดที่ไม่ดีใน Java เนื่องจากตัวเลือกกระบวนทัศน์ได้ทำไปแล้ว อ่าน Bloch EJ2 - เขาระบุไว้อย่างชัดเจนว่าอ้าง (รายการ 57) exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow- ให้คำอธิบายที่ครบถ้วนและครอบคลุมว่าทำไม และเขาเป็นคนเขียน Java lib ดังนั้นเขาจึงเป็นผู้กำหนดสัญญา API ของคลาส / เห็นด้วยกับ Bill K ในอันนี้

8
@ OndraŽižkaหากกรอบงานบางอย่างทำสิ่งนี้ (ใช้ข้อยกเว้นในสภาพที่ไม่พิเศษ) มันมีข้อบกพร่องและแตกหักจากการออกแบบทำลายสัญญาการยกเว้นภาษาของชั้นเรียน เพียงเพราะบางคนเขียนรหัสหมัดไม่ได้ทำให้มันมีหมัดน้อยลง

8
ไม่มีใครนอกจากผู้สร้างของ stackoverflow.com ผิดเกี่ยวกับข้อยกเว้น กฎทองของการพัฒนาซอฟต์แวร์ไม่เคยทำให้ง่ายซับซ้อนและเทอะทะ เขาเขียนว่า: "มันเป็นความจริงว่าสิ่งที่ควรเป็นโปรแกรม 3 บรรทัดง่ายๆมักจะเบ่งบานถึง 48 บรรทัดเมื่อคุณตรวจสอบข้อผิดพลาดได้ดี แต่นั่นคือชีวิต ... " นี่เป็นการค้นหาความบริสุทธิ์ไม่ใช่ความเรียบง่าย
sf_jeff

คำตอบ:


345

ขึ้นอยู่กับว่ามีการใช้ข้อยกเว้นอย่างไร วิธีที่ง่ายที่สุดคือการใช้ setjmp และ longjmp นั่นหมายถึงการลงทะเบียน CPU ทั้งหมดจะถูกเขียนไปยังสแต็ก (ซึ่งใช้เวลาพอสมควร) และอาจจำเป็นต้องสร้างข้อมูลอื่น ๆ ... ทั้งหมดนี้เกิดขึ้นแล้วในคำสั่งลอง คำสั่ง throw ต้องการคลายสแต็กและเรียกคืนค่าของการลงทะเบียนทั้งหมด (และค่าอื่น ๆ ที่เป็นไปได้ใน VM) ดังนั้นลองแล้วโยนช้าพอ ๆ กันและมันก็ค่อนข้างช้า แต่ถ้าไม่มีข้อยกเว้นเกิดขึ้นการออกจากบล็อกลองนั้นจะไม่ใช้เวลาใด ๆ ในกรณีส่วนใหญ่ (เนื่องจากทุกอย่างวางบนสแต็กซึ่งล้างข้อมูลโดยอัตโนมัติหากมีวิธี)

ซันและคนอื่น ๆ ยอมรับว่านี่อาจเป็นสิ่งที่ไม่ดีและแน่นอนว่า VMs จะเร็วขึ้นและเร็วขึ้นเรื่อย ๆ มีวิธีการใช้ข้อยกเว้นอีกวิธีหนึ่งซึ่งทำให้ลองใช้ตัวเองอย่างรวดเร็ว (จริง ๆ แล้วไม่มีอะไรเกิดขึ้นสำหรับการลองใช้โดยทั่วไป - ทุกสิ่งที่ต้องเกิดขึ้นได้ทำไปแล้วเมื่อชั้นเรียนถูกโหลดโดย VM) และทำให้ช้าลง . ฉันไม่รู้ว่า JVM ใช้เทคนิคใหม่ที่ดีกว่านี้ ...

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

นอกจากนี้ยังสร้างความแตกต่างอย่างมากกับสิ่งที่คุณทำในบล็อกลอง หากคุณเปิดบล็อกลองและไม่เคยเรียกวิธีการใด ๆ จากภายในบล็อกทดลองนี้บล็อกทดลองจะเร็วเป็นพิเศษเนื่องจาก JIT จะสามารถจัดการกับการขว้างได้อย่างง่ายดาย มันไม่จำเป็นต้องบันทึกสถานะสแต็กและไม่จำเป็นต้องคลายสแต็กหากมีข้อผิดพลาดเกิดขึ้น (ต้องการเพียงเพื่อข้ามไปยังตัวจัดการ catch) อย่างไรก็ตามนี่ไม่ใช่สิ่งที่คุณมักจะทำ โดยปกติแล้วคุณจะเปิดบล็อกลองแล้วเรียกวิธีที่อาจทำให้เกิดข้อยกเว้นใช่ไหม และแม้ว่าคุณจะใช้ลองบล็อกภายในวิธีการของคุณวิธีการแบบนี้จะเป็นวิธีการที่ไม่เรียกวิธีอื่นใด? มันจะคำนวณตัวเลขหรือไม่? ถ้าอย่างนั้นคุณต้องการข้อยกเว้นอะไร? มีวิธีที่สวยงามกว่ามากในการควบคุมการไหลของโปรแกรม สำหรับสิ่งอื่นใดนอกจากคณิตศาสตร์อย่างง่าย

ดูรหัสทดสอบต่อไปนี้:

public class Test {
    int value;


    public int getValue() {
        return value;
    }

    public void reset() {
        value = 0;
    }

    // Calculates without exception
    public void method1(int i) {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            System.out.println("You'll never see this!");
        }
    }

    // Could in theory throw one, but never will
    public void method2(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            throw new Exception();
        }
    }

    // This one will regularly throw one
    public void method3(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
        // an AND operation between two integers. The size of the number plays
        // no role. AND on 32 BIT always ANDs all 32 bits
        if ((i & 0x1) == 1) {
            throw new Exception();
        }
    }

    public static void main(String[] args) {
        int i;
        long l;
        Test t = new Test();

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            t.method1(i);
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method1 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method2(i);
            } catch (Exception e) {
                System.out.println("You'll never see this!");
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method2 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method3(i);
            } catch (Exception e) {
                // Do nothing here, as we will get here
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method3 took " + l + " ms, result was " + t.getValue()
        );
    }
}

ผลลัพธ์:

method1 took 972 ms, result was 2
method2 took 1003 ms, result was 2
method3 took 66716 ms, result was 2

การชะลอตัวจากบล็อกการลองนั้นเล็กเกินไปที่จะแยกแยะปัจจัยรบกวนเช่นกระบวนการพื้นหลัง แต่บล็อก catch นั้นฆ่าทุกอย่างและทำให้มันช้าลง 66 เท่า!

ดังที่ฉันได้กล่าวไปแล้วผลลัพธ์จะไม่แย่ขนาดนั้นถ้าคุณลอง / จับและโยนทั้งหมดด้วยวิธีเดียวกัน (method3) แต่นี่เป็นการเพิ่มประสิทธิภาพ JIT แบบพิเศษที่ฉันจะไม่พึ่งพา และแม้ว่าจะใช้การเพิ่มประสิทธิภาพนี้การโยนก็ยังค่อนข้างช้า ดังนั้นฉันจึงไม่ทราบว่าคุณกำลังพยายามทำอะไรที่นี่ แต่มีวิธีที่ดีกว่าในการทำเช่นนั้นโดยใช้ลอง / จับ / โยน


7
คำตอบที่ดี แต่ฉันแค่อยากจะเพิ่มว่าเท่าที่ฉันรู้ควรใช้ System.nanoTime () สำหรับการวัดประสิทธิภาพไม่ใช่ System.currentTimeMillis ()
Simon Forsberg

10
@ SimonAndréForsberg nanoTime()ต้องการ Java 1.5 และฉันมี Java 1.4 เท่านั้นที่มีอยู่ในระบบที่ฉันใช้ในการเขียนโค้ดด้านบน นอกจากนี้มันไม่ได้มีบทบาทอย่างมากในทางปฏิบัติ ความแตกต่างเพียงอย่างเดียวระหว่างสองคือหนึ่งนาโนวินาทีและอีกหนึ่งมิลลิวินาทีและnanoTimeไม่ได้รับอิทธิพลจากการปรับเปลี่ยนนาฬิกา (ซึ่งไม่เกี่ยวข้องเว้นแต่คุณหรือกระบวนการของระบบจะปรับเปลี่ยนนาฬิการะบบในทันทีที่รหัสทดสอบกำลังทำงาน) โดยทั่วไปแล้วคุณจะพูดถูกnanoTimeก็เป็นทางเลือกที่ดีกว่า
Mecki

2
ควรสังเกตว่าการทดสอบของคุณเป็นกรณีที่รุนแรง คุณสามารถแสดงประสิทธิภาพตีขนาดเล็กมากสำหรับรหัสกับtryบล็อก throwแต่ไม่มี คุณthrowทดสอบมีการขว้างปาข้อยกเว้น50% ของเวลาที่tryมันผ่านไป ที่เห็นได้ชัดว่าสถานการณ์ที่ความล้มเหลวไม่ได้เป็นพิเศษ การลดขนาดลงเหลือเพียง 10% จะช่วยลดผลกระทบอย่างมาก ปัญหาของการทดสอบประเภทนี้คือการกระตุ้นให้คนหยุดใช้ข้อยกเว้นทั้งหมด การใช้ข้อยกเว้นสำหรับการจัดการกรณีพิเศษมีประสิทธิภาพดีกว่าการทดสอบของคุณอย่างมาก
Nate

1
@Nate แรกของทั้งหมดฉันพูดอย่างชัดเจนว่าทั้งหมดนี้ขึ้นอยู่กับวิธีการใช้งานข้อยกเว้น ฉันเพิ่งทดสอบการใช้งานที่เฉพาะเจาะจงหนึ่งอย่าง แต่ก็มีมากมายและ Oracle อาจเลือกที่แตกต่างกันโดยสิ้นเชิงกับทุกรุ่น ประการที่สองหากข้อยกเว้นมีความพิเศษเพียงอย่างเดียวสิ่งที่พวกเขามักจะเกิดขึ้นแน่นอนว่าผลกระทบนั้นเล็กลงอย่างเห็นได้ชัดว่าฉันไม่คิดว่าจะมีใครชี้ให้เห็นอย่างชัดเจนและทำให้ฉันไม่เข้าใจจุดของคุณเลย และข้อที่สามยกเว้นมากเกินไปมันแย่ทุกคนเห็นด้วยดังนั้นการใช้พวกเขาด้วยความระมัดระวังเป็นสิ่งที่ดีมาก
Mecki

4
@Glide returnโยนไม่ได้เป็นเหมือนที่สะอาด มันทิ้งวิธีไว้ที่ไหนสักแห่งในใจกลางของร่างกายบางทีแม้ในช่วงกลางของการดำเนินการ (ที่ได้ดำเนินการจนถึง 50% เท่านั้น) และcatchบล็อกอาจเป็น 20 กองเฟรมขึ้นไป (วิธีมีtryบล็อกเรียกวิธีที่ 1, ซึ่งเรียกเมธอด 2 ซึ่งเรียก mehtod3, ... และในเมธอด 20 ในระหว่างการดำเนินการข้อยกเว้นจะถูกโยนทิ้ง) สแต็กต้องคลายออก 20 เฟรมขึ้นไปการดำเนินการที่ยังไม่เสร็จทั้งหมดจะต้องเลิกทำ ทั้งหมดนี้ใช้เวลา
Mecki

255

FYI ฉันขยายการทดสอบที่ Mecki ทำ:

method1 took 1733 ms, result was 2
method2 took 1248 ms, result was 2
method3 took 83997 ms, result was 2
method4 took 1692 ms, result was 2
method5 took 60946 ms, result was 2
method6 took 25746 ms, result was 2

3 ตัวแรกนั้นเหมือนกับของ Mecki (แล็ปท็อปของฉันช้าลงอย่างเห็นได้ชัด)

method4 เป็นเหมือน method3 ยกเว้นว่ามันจะสร้างมากกว่าการทำnew Integer(1)throw new Exception()

method5 เป็นเช่น method3 ยกเว้นว่ามันจะสร้างnew Exception()โดยไม่ต้องโยนมัน

method6 นั้นเหมือนกับ method3 ยกเว้นว่ามันจะพ่นข้อยกเว้นที่สร้างไว้ล่วงหน้า (ตัวแปรตัวอย่าง) แทนที่จะสร้างใหม่

ใน Java ค่าใช้จ่ายส่วนใหญ่ของการโยนข้อยกเว้นคือเวลาที่ใช้รวบรวมการติดตามสแต็กซึ่งเกิดขึ้นเมื่อมีการสร้างออบเจ็กต์ข้อยกเว้น ค่าใช้จ่ายจริงของการโยนข้อยกเว้นในขณะที่มีขนาดใหญ่จะน้อยกว่าค่าใช้จ่ายในการสร้างข้อยกเว้น


48
+1 คำตอบของคุณแก้ไขปัญหาหลัก - เวลาที่ใช้ในการคลายและติดตามสแต็กและการโยนข้อผิดพลาดครั้งที่สอง ฉันจะเลือกคำตอบนี้เป็นคำตอบสุดท้าย
วิศวกร

8
ดี ~ 70% สร้างข้อยกเว้น ~ 30% ขว้าง ข้อมูลที่ดี
chaqke

1
@Basil - คุณน่าจะเข้าใจได้จากตัวเลขข้างต้น
Hot Licks

1
นี่อาจเป็นการใช้งานเฉพาะ Java รุ่นใดที่ใช้เป็นเกณฑ์มาตรฐานเหล่านี้
Thorbjørn Ravn Andersen

3
เราสามารถตั้งข้อสังเกตว่าในรหัสมาตรฐานการสร้างและการโยนข้อยกเว้นเกิดขึ้นในกรณีที่เกิดขึ้นได้ยาก (ในกรณีที่ฉันหมายถึง) ถ้าไม่เป็นเช่นนั้นสภาวะของรันไทม์จะแย่มากหรือการออกแบบนั้นเป็นปัญหา ในทั้งสองกรณีการแสดงไม่ใช่เรื่องที่กังวล ...
Jean-Baptiste Yunès

70

Aleksey Shipilëv ทำการวิเคราะห์อย่างละเอียดมากซึ่งเขาอ้างอิงมาตรฐานข้อยกเว้น Java ภายใต้เงื่อนไขต่างๆ:

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

นอกจากนี้เขายังเปรียบเทียบพวกเขากับประสิทธิภาพของการตรวจสอบรหัสข้อผิดพลาดที่ความถี่ข้อผิดพลาดในระดับต่างๆ

ข้อสรุป (คำต่อคำอ้างอิงจากโพสต์ของเขา) คือ:

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

  2. ต้นทุนด้านประสิทธิภาพของข้อยกเว้นมีองค์ประกอบหลักสองประการ: โครงสร้างการติดตามสแต็กเมื่อข้อยกเว้นถูกสร้างอินสแตนซ์และคลี่คลายคลี่คลายในระหว่างการโยนข้อยกเว้น

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

  4. ค่าใช้จ่ายที่คลี่คลายขึ้นอยู่กับว่าเราโชคดีเพียงใดที่นำตัวจัดการข้อยกเว้นเข้ามาใกล้ในโค้ดที่รวบรวม การจัดโครงสร้างโค้ดอย่างระมัดระวังเพื่อหลีกเลี่ยงการค้นหาตัวจัดการข้อยกเว้นอย่างลึกซึ้งอาจช่วยให้เราโชคดีขึ้น

  5. หากเราควรกำจัดทั้งสองอย่างนี้ออกค่าใช้จ่ายของข้อยกเว้นคือสาขาของท้องถิ่น ไม่ว่าจะฟังดูไพเราะมากเพียงใดก็ตามนั่นไม่ได้หมายความว่าคุณควรใช้ข้อยกเว้นเป็นโฟลว์ควบคุมตามปกติเพราะในกรณีนี้คุณจะต้องใช้คอมไพเลอร์ให้เกิดประโยชน์สูงสุด! คุณควรใช้พวกเขาในกรณีพิเศษอย่างแท้จริงที่ความถี่ยกเว้นตัดจำหน่ายค่าใช้จ่ายโชคร้ายที่เป็นไปได้ของการเพิ่มข้อยกเว้นที่เกิดขึ้นจริง

  6. กฎของหัวแม่มือในแง่ดีนั้นน่าจะมีความถี่10 ^ -4สำหรับข้อยกเว้นนั้นยอดเยี่ยมมาก แน่นอนว่าขึ้นอยู่กับน้ำหนักที่หนักของข้อยกเว้นตัวเองการกระทำที่ถูกต้องในตัวจัดการข้อยกเว้น ฯลฯ

ผลที่สุดคือเมื่อไม่มีข้อยกเว้นเกิดขึ้นคุณจะไม่ต้องเสียค่าใช้จ่ายดังนั้นเมื่อเงื่อนไขพิเศษคือการจัดการข้อยกเว้นที่หายากเพียงพอจะเร็วกว่าการใช้ifทุกครั้ง โพสต์แบบเต็มมีมูลค่ามากอ่าน


41

น่าเสียดายที่คำตอบของฉันยาวเกินไปที่จะโพสต์ที่นี่ ดังนั้นให้ฉันสรุปที่นี่และแนะนำคุณไปที่http://www.fuwjax.com/how-slow-are-java-exceptions/สำหรับรายละเอียดที่น่ากลัว

คำถามจริงที่นี่ไม่ใช่ "ความล้มเหลวที่รายงานเป็นข้อยกเว้น 'ช้าเพียงใดเมื่อเทียบกับ' โค้ดที่ไม่เคยล้มเหลว '? ในขณะที่คำตอบที่ยอมรับอาจทำให้คุณเชื่อ แต่คำถามควรเป็น "ความล้มเหลวรายงานเป็นข้อยกเว้น" ช้าเพียงใดเมื่อเทียบกับความล้มเหลวที่รายงานวิธีอื่น ๆ " โดยทั่วไปแล้ววิธีการรายงานความล้มเหลวอีกสองวิธีนั้นอาจมีค่า Sentinel หรือมีการล้อมรอบผลลัพธ์

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

ปรากฎว่ามีความเสี่ยงต่อความปลอดภัยของประเภทค่า Sentinel นั้นเร็วกว่าข้อยกเว้น แต่เพียง 2 เท่าโดยประมาณ ตอนนี้อาจดูเหมือนมาก แต่ 2x นั้นครอบคลุมค่าใช้จ่ายของความแตกต่างในการใช้งานเท่านั้น ในทางปฏิบัติปัจจัยต่ำกว่ามากเนื่องจากวิธีการของเราที่อาจล้มเหลวนั้นน่าสนใจกว่าตัวดำเนินการทางคณิตศาสตร์เพียงเล็กน้อยในรหัสตัวอย่างที่อื่นในหน้านี้

ในทางกลับกันการห่อผลลัพธ์ไม่ต้องเสียสละความปลอดภัยเลย พวกเขาห่อข้อมูลความสำเร็จและความล้มเหลวในชั้นเรียนเดียว ดังนั้นแทนที่จะเป็น "instanceof" พวกเขาจึงให้ "isSuccess ()" และ getters สำหรับทั้งวัตถุที่สำเร็จและล้มเหลว อย่างไรก็ตามวัตถุผลลัพธ์นั้นช้ากว่าการใช้ข้อยกเว้นประมาณ 2 เท่า ปรากฎว่าการสร้างวัตถุห่อหุ้มใหม่ทุกครั้งมีราคาแพงกว่าการโยนข้อยกเว้นบางครั้ง

ยิ่งไปกว่านั้นข้อยกเว้นคือภาษาที่ให้วิธีการระบุว่าวิธีการอาจล้มเหลว ไม่มีวิธีอื่นที่จะบอกได้จากเพียงแค่ API ที่คาดว่าวิธีการ (ส่วนใหญ่) งานเสมอและที่คาดว่าจะรายงานความล้มเหลว

ข้อยกเว้นนั้นปลอดภัยกว่ารักษาการณ์เร็วกว่าวัตถุผลลัพธ์และทำให้ประหลาดใจน้อยกว่า ฉันไม่แนะนำให้ลอง / catch แทนที่หาก / else แต่ข้อยกเว้นเป็นวิธีที่เหมาะสมในการรายงานความล้มเหลวแม้ในตรรกะทางธุรกิจ

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


5
ฉันตัดสินใจที่จะทดสอบประสิทธิภาพระยะยาวของการใช้งานทั้งสามเมื่อเปรียบเทียบกับการใช้งานการควบคุมที่ตรวจสอบความล้มเหลวโดยไม่ต้องรายงาน กระบวนการนี้มีอัตราความล้มเหลวประมาณ 4% การทำซ้ำการทดสอบจะเรียกใช้กระบวนการ 10,000 ครั้งกับหนึ่งในกลยุทธ์ แต่ละกลยุทธ์มีการทดสอบ 1,000 ครั้งและ 900 ครั้งสุดท้ายจะถูกใช้เพื่อสร้างสถิติ นี่คือเวลาเฉลี่ยใน nanos: การควบคุม 338 ข้อยกเว้น 429 ผลลัพธ์ 348 Sentinel 345
Fuwjax

2
เพื่อความสนุกฉันปิดการใช้งาน fillInStackTrace ในการทดสอบข้อยกเว้น นี่คือเวลาตอนนี้: การควบคุม 347 ข้อยกเว้น 351 ผล 364 Sentinel 355
Fuwjax

Fuwjax เว้นแต่ว่าฉันจะพลาดอะไรบางอย่าง (และฉันยอมรับว่าฉันได้อ่านโพสต์ SO ของคุณเท่านั้นไม่ใช่โพสต์บล็อกของคุณ) ดูเหมือนว่าความคิดเห็นทั้งสองของคุณจะขัดแย้งกับโพสต์ของคุณ ฉันคิดว่าตัวเลขที่ต่ำกว่าดีกว่าในเกณฑ์มาตรฐานของคุณใช่ไหม ในกรณีนี้การสร้างข้อยกเว้นด้วยการเปิดใช้งาน fillInStackTrace (ซึ่งเป็นค่าเริ่มต้นและพฤติกรรมปกติ) ส่งผลให้ประสิทธิภาพการทำงานช้าลงกว่าอีกสองเทคนิคที่คุณอธิบาย ฉันขาดอะไรไปหรือเปล่าหรือคุณแสดงความคิดเห็นเพื่อหักล้างโพสต์ของคุณ?
เฟลิกซ์ GV

@Fuwjax - วิธีหลีกเลี่ยงตัวเลือก "rock and hard place" ที่คุณนำเสนอที่นี่คือการจัดสรรวัตถุที่แสดงถึง "ความสำเร็จ" ล่วงหน้า โดยปกติแล้วหนึ่งสามารถจัดสรรวัตถุล่วงหน้าสำหรับกรณีความล้มเหลวทั่วไป จากนั้นเฉพาะในกรณีที่หายากของการส่งกลับรายละเอียดเพิ่มเติมเป็นวัตถุใหม่ที่สร้างขึ้น (นี่คือ OO เทียบเท่ากับ "รหัสข้อผิดพลาด" จำนวนเต็มบวกกับการโทรแยกต่างหากเพื่อรับรายละเอียดของข้อผิดพลาดครั้งสุดท้าย - เทคนิคที่มีมานานหลายทศวรรษ)
ToolmakerSteve

@Fuwjax การโยนข้อยกเว้นไม่ได้สร้างวัตถุด้วยบัญชีของคุณ? ไม่แน่ใจว่าฉันเข้าใจเหตุผลนั้น ไม่ว่าคุณจะโยนข้อยกเว้นหรือส่งคืนวัตถุผลลัพธ์คุณกำลังสร้างวัตถุ ในความหมายนั้นวัตถุผลลัพธ์จะไม่ช้ากว่าการโยนข้อยกเว้น
Matthias

20

ฉันได้ขยายคำตอบที่ได้รับจาก@Meckiและ@incarnateโดยไม่ต้องเติม stacktrace สำหรับ Java

ด้วย Java 7+ Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace)เราสามารถใช้ แต่สำหรับ Java6 ดูคำตอบของฉันสำหรับคำถามนี้

// This one will regularly throw one
public void method4(int i) throws NoStackTraceThrowable {
    value = ((value + i) / i) << 1;
    // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
    // an AND operation between two integers. The size of the number plays
    // no role. AND on 32 BIT always ANDs all 32 bits
    if ((i & 0x1) == 1) {
        throw new NoStackTraceThrowable();
    }
}

// This one will regularly throw one
public void method5(int i) throws NoStackTraceRuntimeException {
    value = ((value + i) / i) << 1;
    // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
    // an AND operation between two integers. The size of the number plays
    // no role. AND on 32 BIT always ANDs all 32 bits
    if ((i & 0x1) == 1) {
        throw new NoStackTraceRuntimeException();
    }
}

public static void main(String[] args) {
    int i;
    long l;
    Test t = new Test();

    l = System.currentTimeMillis();
    t.reset();
    for (i = 1; i < 100000000; i++) {
        try {
            t.method4(i);
        } catch (NoStackTraceThrowable e) {
            // Do nothing here, as we will get here
        }
    }
    l = System.currentTimeMillis() - l;
    System.out.println( "method4 took " + l + " ms, result was " + t.getValue() );


    l = System.currentTimeMillis();
    t.reset();
    for (i = 1; i < 100000000; i++) {
        try {
            t.method5(i);
        } catch (RuntimeException e) {
            // Do nothing here, as we will get here
        }
    }
    l = System.currentTimeMillis() - l;
    System.out.println( "method5 took " + l + " ms, result was " + t.getValue() );
}

เอาต์พุตด้วย Java 1.6.0_45 บน Core i7, 8GB RAM:

method1 took 883 ms, result was 2
method2 took 882 ms, result was 2
method3 took 32270 ms, result was 2 // throws Exception
method4 took 8114 ms, result was 2 // throws NoStackTraceThrowable
method5 took 8086 ms, result was 2 // throws NoStackTraceRuntimeException

ดังนั้นยังคงวิธีการที่ส่งกลับค่าได้เร็วขึ้นเมื่อเทียบกับวิธีการโยนข้อยกเว้น IMHO เราไม่สามารถออกแบบ API ที่ชัดเจนเพียงแค่ใช้ประเภทผลตอบแทนสำหรับความสำเร็จและการไหลผิดพลาด วิธีการที่ใช้การยกเว้นโดยไม่มี stacktrace จะเร็วกว่าการยกเว้นปกติ 4-5 เท่า

แก้ไข: NoStackTraceThrowable.java ขอบคุณ @Greg

public class NoStackTraceThrowable extends Throwable { 
    public NoStackTraceThrowable() { 
        super("my special throwable", null, false, false);
    }
}

น่าสนใจขอบคุณ นี่คือการประกาศคลาสขาดหายไป:public class NoStackTraceThrowable extends Throwable { public NoStackTraceThrowable() { super("my special throwable", null, false, false); } }
เกร็ก

ที่ beginging คุณเขียนWith Java 7+, we can useแต่หลังจากนั้นคุณเขียนOutput with Java 1.6.0_45,ดังนั้นนี่คือ Java 6 หรือ 7 ผลลัพธ์?
WBAR

1
@WBAR จาก Java 7 เราแค่ต้องใช้ThrowableConstructor ที่มีboolean writableStackTraceอาร์กิวเมนต์ แต่นั่นไม่ปรากฏใน Java 6 และด้านล่าง นั่นคือเหตุผลที่ฉันได้กำหนดให้ใช้งานแบบกำหนดเองสำหรับ Java 6 และด้านล่าง ดังนั้นรหัสข้างต้นสำหรับ Java 6 และด้านล่าง โปรดอ่านบรรทัดที่ 1 ของ 2nd para อย่างระมัดระวัง
manikanta

@manikanta "IMHO เราไม่สามารถออกแบบ API ที่ชัดเจนเพียงแค่ใช้ชนิดส่งคืนสำหรับความสำเร็จและการไหลของข้อผิดพลาด" - เราทำได้ถ้าเราใช้ตัวเลือก / ผลลัพธ์ / อาจเป็นหลายภาษา
Hejazzman

@ Hejazzman ฉันเห็นด้วย แต่Optionalหรือคล้ายกันช้าไป Java ก่อนหน้านั้นเรายังใช้วัตถุห่อหุ้มที่มีค่าสถานะความสำเร็จ / ข้อผิดพลาด แต่ดูเหมือนว่าจะเป็นการแฮ็กเล็กน้อยและไม่รู้สึกเป็นธรรมชาติสำหรับฉัน
manikanta

8

ในขณะที่หลังฉันเขียนชั้นเรียนเพื่อทดสอบประสิทธิภาพสัมพัทธ์ของการแปลงสตริงเป็น int โดยใช้สองวิธี: (1) โทร Integer.parseInt () และจับข้อยกเว้นหรือ (2) จับคู่สตริงกับ regex และโทร parseInt () เฉพาะในกรณีที่การแข่งขันประสบความสำเร็จ ฉันใช้ regex อย่างมีประสิทธิภาพมากที่สุดเท่าที่จะทำได้ (เช่นการสร้างวัตถุ Pattern และ Matcher ก่อนที่จะวนลูป) และฉันไม่ได้พิมพ์หรือบันทึก stacktraces จากข้อยกเว้น

สำหรับรายการของสตริงหมื่นถ้าพวกเขาเป็นตัวเลขที่ถูกต้องทั้งหมดวิธี parseInt () เป็นวิธีที่เร็วเป็นสี่เท่าของวิธีการ regex แต่ถ้ามีเพียง 80% ของสตริงที่ถูกต้อง regex นั้นเร็วกว่า parseInt () สองเท่า และถ้าถูกต้อง 20% หมายความว่ามีการโยนข้อยกเว้นและจับ 80% ของเวลา Regex นั้นเร็วกว่า parseInt ประมาณยี่สิบเท่า

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


คุณใช้ JVM ประเภทใด มันยังช้าอยู่ไหมเมื่อใช้ sun-jdk 6
Benedikt Waldvogel

ฉันขุดขึ้นมาแล้ววิ่งอีกครั้งภายใต้ JDK 1.6u10 ก่อนที่จะส่งคำตอบนั้นและนั่นคือผลลัพธ์ที่ฉันโพสต์
Alan Moore

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

8

ฉันคิดว่าบทความแรกอ้างถึงการกระทำของ traversing call stack และสร้าง stack trace ว่าเป็นส่วนที่มีราคาแพงและในขณะที่บทความที่สองไม่ได้พูดฉันคิดว่านั่นเป็นส่วนที่แพงที่สุดในการสร้างวัตถุ จอห์นโรสมีบทความที่เขาอธิบายถึงเทคนิคที่แตกต่างกันสำหรับการเร่งขึ้นยกเว้น (การจัดสรรล่วงหน้าและการนำข้อยกเว้นมาใช้ใหม่ข้อยกเว้นที่ไม่มีการติดตามสแต็ก ฯลฯ )

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

Microbenchmarks ใน Java นั้นยากที่จะทำให้ถูกต้อง (โดยเฉพาะอย่างยิ่งเมื่อฉันได้รับแจ้ง) โดยเฉพาะอย่างยิ่งเมื่อคุณเข้าไปในอาณาเขตของ JIT ดังนั้นฉันจึงสงสัยว่าการใช้ข้อยกเว้นนั้นเร็วกว่า "คืน" ในชีวิตจริง ตัวอย่างเช่นฉันสงสัยว่าคุณมีบางระหว่าง 2 และ 5 เฟรมสแต็คในการทดสอบของคุณ? ตอนนี้คิดว่าโค้ดของคุณจะถูกเรียกใช้โดยองค์ประกอบ JSF ที่ปรับใช้โดย JBoss ตอนนี้คุณอาจมีการติดตามสแต็กซึ่งมีหลายหน้ายาว

บางทีคุณสามารถโพสต์รหัสทดสอบของคุณ?


7

ไม่ทราบว่าหัวข้อเหล่านี้เกี่ยวข้องหรือไม่ แต่ฉันเคยต้องการใช้เคล็ดลับหนึ่งอย่างโดยอาศัยการติดตามสแต็กของเธรดปัจจุบัน: ฉันต้องการค้นหาชื่อของวิธีการที่กระตุ้นการสร้างอินสแตนซ์ภายในคลาส instantiated (yeap ความคิดบ้า ฉันยอมแพ้ทั้งหมด) ดังนั้นผมจึงค้นพบเรียกว่าThread.currentThread().getStackTrace()เป็นมากช้า (เนื่องจากพื้นเมืองของdumpThreadsวิธีการที่จะใช้ภายใน)

ดังนั้น Java ThrowableมีวิธีการเนfillInStackTraceทีฟ ฉันคิดว่านักฆ่าcatchบล็อกที่ได้อธิบายไปก่อนหน้านี้จะกระตุ้นการดำเนินการของวิธีนี้

แต่ให้ฉันเล่าเรื่องอื่นให้คุณ ...

ใน Scala คุณสมบัติการทำงานบางอย่างจะถูกรวบรวมใน JVM โดยใช้ControlThrowableซึ่งขยายThrowableและแทนที่fillInStackTraceในวิธีต่อไปนี้:

override def fillInStackTrace(): Throwable = this

ดังนั้นฉันจึงปรับการทดสอบด้านบน (จำนวนรอบลดลงสิบเครื่องของฉันช้าลงเล็กน้อย :):

class ControlException extends ControlThrowable

class T {
  var value = 0

  def reset = {
    value = 0
  }

  def method1(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      println("You'll never see this!")
    }
  }

  def method2(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      throw new Exception()
    }
  }

  def method3(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new Exception()
    }
  }

  def method4(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new ControlException()
    }
  }
}

class Main {
  var l = System.currentTimeMillis
  val t = new T
  for (i <- 1 to 10000000)
    t.method1(i)
  l = System.currentTimeMillis - l
  println("method1 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method2(i)
  } catch {
    case _ => println("You'll never see this")
  }
  l = System.currentTimeMillis - l
  println("method2 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method4(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method4 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method3(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method3 took " + l + " ms, result was " + t.value)

}

ดังนั้นผลลัพธ์คือ:

method1 took 146 ms, result was 2
method2 took 159 ms, result was 2
method4 took 1551 ms, result was 2
method3 took 42492 ms, result was 2

คุณเห็นความแตกต่างเพียงอย่างเดียวระหว่างmethod3และmethod4คือพวกมันมีข้อยกเว้นประเภทต่าง ๆ Yeap method4ยังช้ากว่าmethod1และmethod2แตกต่างกัน แต่ยอมรับได้มากกว่า


6

ฉันได้ทำการทดสอบประสิทธิภาพกับ JVM 1.5 แล้วและการใช้ข้อยกเว้นนั้นช้ากว่าอย่างน้อย 2x โดยเฉลี่ย: เวลาดำเนินการในวิธีเล็ก ๆ น้อย ๆ มากกว่าสามเท่า (3x) โดยมีข้อยกเว้น การวนซ้ำขนาดเล็กที่ต้องตรวจจับข้อผิดพลาดนั้นเพิ่มขึ้น 2 เท่าในเวลาตัวเอง

ฉันเห็นตัวเลขที่คล้ายกันในรหัสการผลิตและการวัดขนาดเล็ก

ข้อยกเว้นไม่ควรใช้กับทุกสิ่งที่ถูกเรียกเป็นประจำ การขว้างข้อยกเว้นนับพันครั้งต่อวินาทีจะทำให้คอขวดขนาดใหญ่

ตัวอย่างเช่นการใช้ "Integer.ParseInt (... )" เพื่อค้นหาค่าที่ไม่ดีทั้งหมดในไฟล์ข้อความที่มีขนาดใหญ่มาก - แนวคิดที่แย่มาก (ฉันได้เห็นวิธีอรรถประโยชน์นี้ฆ่าประสิทธิภาพในรหัสการผลิต)

การใช้ข้อยกเว้นเพื่อรายงานค่าที่ไม่ดีในรูปแบบ GUI ผู้ใช้อาจไม่ได้แย่มากจากจุดยืนด้านประสิทธิภาพ

ไม่ว่าจะเป็นการออกแบบที่ดีหรือไม่ฉันก็จะไปกับกฎ: หากข้อผิดพลาดเป็นเรื่องปกติ / คาดว่าจะใช้ค่าตอบแทน ถ้ามันผิดปกติให้ใช้ข้อยกเว้น ตัวอย่างเช่น: การอ่านอินพุตของผู้ใช้ค่าที่ไม่ดีเป็นเรื่องปกติ - ใช้รหัสข้อผิดพลาด การส่งผ่านค่าไปยังฟังก์ชันยูทิลิตี้ภายในควรกรองค่าที่ไม่ดีโดยใช้รหัสโทรศัพท์ - ใช้ข้อยกเว้น


ให้ฉันแนะนำบางสิ่งที่ทำได้ดีถ้าคุณต้องการตัวเลขในรูปแบบแทนที่จะใช้ Integer.valueOf (String) คุณควรพิจารณาใช้ expression matcher แทน คุณสามารถคอมไพล์ล่วงหน้าและนำรูปแบบกลับมาใช้ใหม่ได้ทำให้การจับคู่มีราคาถูก อย่างไรก็ตามในรูปแบบ GUI การมี isValid / validate / checkField หรือสิ่งที่คุณอาจชัดเจนกว่า นอกจากนี้ด้วย Java 8 เรามี Monads ซึ่งเป็นทางเลือกดังนั้นให้พิจารณาใช้ (คำตอบคือ 9 ปี แต่ยังคง!: p)
Haakon Løtveit

4

ประสิทธิภาพของข้อยกเว้นใน Java และ C # เป็นที่ต้องการอย่างมาก

ในฐานะผู้เขียนโปรแกรมสิ่งนี้บังคับให้เราดำเนินชีวิตตามกฎ "ข้อยกเว้นควรเกิดขึ้นไม่บ่อยนัก" เพียงเพื่อเหตุผลด้านประสิทธิภาพ

อย่างไรก็ตามในฐานะนักวิทยาศาสตร์คอมพิวเตอร์เราควรต่อต้านรัฐบาลที่มีปัญหานี้ คนที่เขียนฟังก์ชั่นมักจะไม่รู้ว่ามันจะถูกเรียกบ่อยแค่ไหนหรือว่าความสำเร็จหรือความล้มเหลวนั้นมีแนวโน้มมากขึ้น มีเพียงผู้โทรเท่านั้นที่มีข้อมูลนี้ การพยายามหลีกเลี่ยงข้อยกเว้นนำไปสู่การที่ไม่ชัดเจนของ API idoms ซึ่งในบางกรณีเรามีเฉพาะรุ่นคลีน แต่ช้ายกเว้นและในกรณีอื่น ๆ เรามีข้อผิดพลาดที่ส่งกลับค่าอย่างรวดเร็ว แต่ไม่น่าสนใจและในกรณีอื่น ๆ . ผู้พัฒนาไลบรารีอาจต้องเขียนและดูแล API สองเวอร์ชันและผู้เรียกต้องตัดสินใจว่าจะใช้สองเวอร์ชันใดในแต่ละสถานการณ์

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

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

นี่คือตัวอย่างโค้ดที่เปรียบเทียบประสิทธิภาพข้อยกเว้นกับประสิทธิภาพข้อผิดพลาด - คืน - ค่า

ระดับสาธารณะ TestIt {

int value;


public int getValue() {
    return value;
}

public void reset() {
    value = 0;
}

public boolean baseline_null(boolean shouldfail, int recurse_depth) {
    if (recurse_depth <= 0) {
        return shouldfail;
    } else {
        return baseline_null(shouldfail,recurse_depth-1);
    }
}

public boolean retval_error(boolean shouldfail, int recurse_depth) {
    if (recurse_depth <= 0) {
        if (shouldfail) {
            return false;
        } else {
            return true;
        }
    } else {
        boolean nested_error = retval_error(shouldfail,recurse_depth-1);
        if (nested_error) {
            return true;
        } else {
            return false;
        }
    }
}

public void exception_error(boolean shouldfail, int recurse_depth) throws Exception {
    if (recurse_depth <= 0) {
        if (shouldfail) {
            throw new Exception();
        }
    } else {
        exception_error(shouldfail,recurse_depth-1);
    }

}

public static void main(String[] args) {
    int i;
    long l;
    TestIt t = new TestIt();
    int failures;

    int ITERATION_COUNT = 100000000;


    // (0) baseline null workload
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                t.baseline_null(shoulderror,recurse_depth);
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("baseline: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);
        }
    }


    // (1) retval_error
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                if (!t.retval_error(shoulderror,recurse_depth)) {
                    failures++;
                }
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("retval_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);
        }
    }

    // (2) exception_error
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                try {
                    t.exception_error(shoulderror,recurse_depth);
                } catch (Exception e) {
                    failures++;
                }
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("exception_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);              
        }
    }
}

}

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

baseline: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 683 ms
baseline: recurse_depth 2, exception_freqeuncy 0.25 (0), time elapsed 790 ms
baseline: recurse_depth 2, exception_freqeuncy 0.5 (0), time elapsed 768 ms
baseline: recurse_depth 2, exception_freqeuncy 0.75 (0), time elapsed 749 ms
baseline: recurse_depth 2, exception_freqeuncy 1.0 (0), time elapsed 731 ms
baseline: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 923 ms
baseline: recurse_depth 5, exception_freqeuncy 0.25 (0), time elapsed 971 ms
baseline: recurse_depth 5, exception_freqeuncy 0.5 (0), time elapsed 982 ms
baseline: recurse_depth 5, exception_freqeuncy 0.75 (0), time elapsed 947 ms
baseline: recurse_depth 5, exception_freqeuncy 1.0 (0), time elapsed 937 ms
baseline: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1154 ms
baseline: recurse_depth 8, exception_freqeuncy 0.25 (0), time elapsed 1149 ms
baseline: recurse_depth 8, exception_freqeuncy 0.5 (0), time elapsed 1133 ms
baseline: recurse_depth 8, exception_freqeuncy 0.75 (0), time elapsed 1117 ms
baseline: recurse_depth 8, exception_freqeuncy 1.0 (0), time elapsed 1116 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 742 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 743 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 734 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 723 ms
retval_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 728 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 920 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 1121   ms
retval_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 1037 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 1141   ms
retval_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 1130 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1218 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 1334  ms
retval_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 1478 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 1637 ms
retval_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 1655 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 726 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 17487   ms
exception_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 33763   ms
exception_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 67367   ms
exception_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 66990 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 924 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 23775  ms
exception_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 46326 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 91707 ms
exception_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 91580 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1144 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 30440 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 59116   ms
exception_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 116678 ms
exception_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 116477 ms

การตรวจสอบและเผยแพร่ค่าตอบแทนจะเพิ่มค่าใช้จ่ายบางอย่างเทียบกับการโทรพื้นฐาน - null และค่าใช้จ่ายนั้นเป็นสัดส่วนกับความลึกการโทร ที่ระดับความลึกสายเรียกเข้า 8 เวอร์ชันการตรวจสอบข้อผิดพลาดคืนค่านั้นช้ากว่ารุ่นพื้นฐานประมาณ 27% ซึ่งไม่ได้ตรวจสอบค่าส่งคืน

ในการเปรียบเทียบข้อยกเว้นไม่ได้เป็นฟังก์ชั่นการโทรเชิงลึก แต่เป็นข้อยกเว้นความถี่ อย่างไรก็ตามการลดลงเมื่อความถี่ในการยกเว้นเพิ่มขึ้นอย่างมาก ที่ความถี่ข้อผิดพลาดเพียง 25% รหัสจะทำงาน 24 ครั้งช้ากว่า ที่ความถี่ข้อผิดพลาด 100% เวอร์ชันยกเว้นจะช้ากว่า 100 ครั้ง

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


3

HotSpot ค่อนข้างสามารถลบรหัสข้อยกเว้นสำหรับข้อยกเว้นที่ระบบสร้างขึ้นได้ตราบใดที่มีการ inline ทั้งหมด อย่างไรก็ตามข้อยกเว้นที่สร้างขึ้นอย่างชัดเจนและข้อยกเว้นอื่น ๆ ที่ไม่ถูกลบใช้เวลาสร้างการติดตามสแต็ก แทนที่fillInStackTraceเพื่อดูว่าสิ่งนี้มีผลต่อประสิทธิภาพการทำงานอย่างไร


2

แม้ว่าการโยนข้อยกเว้นจะไม่ช้า แต่ก็เป็นความคิดที่ดีที่จะโยนข้อยกเว้นสำหรับโฟลว์โปรแกรมปกติ ใช้วิธีนี้คล้ายกับ GOTO ...

ฉันเดาว่าไม่ได้ตอบคำถามจริงๆ ฉันคิดว่าภูมิปัญญา 'ธรรมดา' ของการโยนข้อยกเว้นที่ช้านั้นเป็นจริงในจาวาเวอร์ชันก่อนหน้า (<1.4) การสร้างข้อยกเว้นต้องการให้ VM สร้างการติดตามสแต็กทั้งหมด มีการเปลี่ยนแปลงมากมายตั้งแต่นั้นมาใน VM เพื่อเร่งความเร็วสิ่งต่าง ๆ และเป็นไปได้ที่จะมีการปรับปรุงพื้นที่หนึ่ง


1
มันจะเป็นการดีที่จะกำหนด "โปรแกรมปกติไหล" มีการเขียนจำนวนมากเกี่ยวกับการใช้ข้อยกเว้นที่ถูกตรวจสอบว่าเป็นความล้มเหลวของกระบวนการทางธุรกิจและข้อยกเว้นที่ไม่ได้ตรวจสอบสำหรับความล้มเหลวที่ไม่สามารถกู้คืนได้ดังนั้นในแง่หนึ่งความล้มเหลวในตรรกะทางธุรกิจ
Spencer Kormos

2
@Spencer K: ข้อยกเว้นตามชื่อที่แสดงถึงหมายความว่ามีการค้นพบสถานการณ์พิเศษ (ไฟล์หายไปเครือข่ายปิดทันที ... ) นี่ก็หมายความว่าสถานการณ์ไม่คาดคิด หากคาดว่าสถานการณ์จะเกิดขึ้นฉันจะไม่ใช้ข้อยกเว้น
Mecki

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

2
ในแง่ของการควบคุมการไหลข้อยกเว้นคือคล้ายกับbreakหรือไม่return goto
Hot Licks

3
มีกระบวนทัศน์การเขียนโปรแกรมมากมาย ไม่สามารถมี“ การไหลปกติ” เดียวไม่ว่าคุณจะหมายถึงอะไร โดยทั่วไปกลไกการยกเว้นเป็นเพียงวิธีที่จะออกจากเฟรมปัจจุบันอย่างรวดเร็วและคลายสแต็กจนกว่าจะถึงจุดหนึ่ง คำว่า "ข้อยกเว้น" ไม่ได้มีความหมายอะไรเกี่ยวกับธรรมชาติที่“ ไม่คาดฝัน” ของมัน ตัวอย่างอย่างรวดเร็ว: เป็นเรื่องธรรมดามากที่จะ“ โยน” 404s จากเว็บแอปพลิเคชั่นเมื่อเกิดเหตุการณ์บางอย่างขึ้นระหว่างเส้นทาง ทำไมจะไม่ใช้ตรรกะนั้นกับข้อยกเว้น? แอนตี้ - รูปแบบคืออะไร?
จุติลง

2

เพียงเปรียบเทียบสมมติว่า Integer.parseInt กับวิธีการต่อไปนี้ซึ่งเพิ่งส่งกลับค่าเริ่มต้นในกรณีของข้อมูลที่ไม่สามารถค้นหาได้แทนการทิ้งข้อยกเว้น:

  public static int parseUnsignedInt(String s, int defaultValue) {
    final int strLength = s.length();
    if (strLength == 0)
      return defaultValue;
    int value = 0;
    for (int i=strLength-1; i>=0; i--) {
      int c = s.charAt(i);
      if (c > 47 && c < 58) {
        c -= 48;
        for (int j=strLength-i; j!=1; j--)
          c *= 10;
        value += c;
      } else {
        return defaultValue;
      }
    }
    return value < 0 ? /* übergebener wert > Integer.MAX_VALUE? */ defaultValue : value;
  }

ตราบใดที่คุณใช้ทั้งสองวิธีกับข้อมูล "ที่ถูกต้อง" พวกเขาทั้งสองจะทำงานในอัตราเดียวกันโดยประมาณ (แม้ว่า Integer.parseInt จะจัดการกับข้อมูลที่ซับซ้อนมากขึ้น) แต่ทันทีที่คุณพยายามแยกวิเคราะห์ข้อมูลที่ไม่ถูกต้อง (เช่นการแยกวิเคราะห์ "abc" 1.000.000 ครั้ง) ความแตกต่างของประสิทธิภาพควรเป็นสิ่งจำเป็น


2

โพสต์ที่ยอดเยี่ยมเกี่ยวกับประสิทธิภาพของข้อยกเว้นคือ:

https://shipilev.net/blog/2014/exceptional-performance/

การสร้างอินสแตนซ์ vs การใช้ซ้ำที่มีอยู่ด้วยการติดตามสแต็กและไม่มี ฯลฯ :

Benchmark                            Mode   Samples         Mean   Mean error  Units

dynamicException                     avgt        25     1901.196       14.572  ns/op
dynamicException_NoStack             avgt        25       67.029        0.212  ns/op
dynamicException_NoStack_UsedData    avgt        25       68.952        0.441  ns/op
dynamicException_NoStack_UsedStack   avgt        25      137.329        1.039  ns/op
dynamicException_UsedData            avgt        25     1900.770        9.359  ns/op
dynamicException_UsedStack           avgt        25    20033.658      118.600  ns/op

plain                                avgt        25        1.259        0.002  ns/op
staticException                      avgt        25        1.510        0.001  ns/op
staticException_NoStack              avgt        25        1.514        0.003  ns/op
staticException_NoStack_UsedData     avgt        25        4.185        0.015  ns/op
staticException_NoStack_UsedStack    avgt        25       19.110        0.051  ns/op
staticException_UsedData             avgt        25        4.159        0.007  ns/op
staticException_UsedStack            avgt        25       25.144        0.186  ns/op

ขึ้นอยู่กับความลึกของการติดตามสแต็ก:

Benchmark        Mode   Samples         Mean   Mean error  Units

exception_0000   avgt        25     1959.068       30.783  ns/op
exception_0001   avgt        25     1945.958       12.104  ns/op
exception_0002   avgt        25     2063.575       47.708  ns/op
exception_0004   avgt        25     2211.882       29.417  ns/op
exception_0008   avgt        25     2472.729       57.336  ns/op
exception_0016   avgt        25     2950.847       29.863  ns/op
exception_0032   avgt        25     4416.548       50.340  ns/op
exception_0064   avgt        25     6845.140       40.114  ns/op
exception_0128   avgt        25    11774.758       54.299  ns/op
exception_0256   avgt        25    21617.526      101.379  ns/op
exception_0512   avgt        25    42780.434      144.594  ns/op
exception_1024   avgt        25    82839.358      291.434  ns/op

สำหรับรายละเอียดอื่น ๆ (รวมถึง x64 Assembler จาก JIT) อ่านโพสต์บล็อกต้นฉบับ

นั่นหมายถึง Hibernate / Spring / etc-EE-shit ช้าเนื่องจากข้อยกเว้น (xD) และการเขียนการควบคุมแอพใหม่ให้ไหลออกจากข้อยกเว้น (แทนที่ด้วยcontinure/ breakและการคืนbooleanค่าสถานะเช่นใน C จากการเรียกใช้เมธอด) ปรับปรุงประสิทธิภาพของแอพพลิเคชัน 10x-100x ขึ้นอยู่กับว่าคุณโยนมันบ่อยแค่ไหน))


0

ฉันเปลี่ยนคำตอบของ @Mecki ด้านบนเพื่อให้ method1 คืนค่าบูลีนและตรวจสอบวิธีการเรียกใช้เนื่องจากคุณไม่สามารถแทนที่ข้อยกเว้นด้วยอะไรก็ได้ หลังจากการวิ่งสองครั้ง method1 ยังคงเร็วที่สุดหรือเร็วที่สุดเท่าที่ method2

นี่คือภาพรวมของรหัส:

// Calculates without exception
public boolean method1(int i) {
    value = ((value + i) / i) << 1;
    // Will never be true
    return ((i & 0xFFFFFFF) == 1000000000);

}
....
   for (i = 1; i < 100000000; i++) {
            if (t.method1(i)) {
                System.out.println("Will never be true!");
            }
    }

และผลลัพธ์:

เรียกใช้ 1

method1 took 841 ms, result was 2
method2 took 841 ms, result was 2
method3 took 85058 ms, result was 2

เรียกใช้ 2

method1 took 821 ms, result was 2
method2 took 838 ms, result was 2
method3 took 85929 ms, result was 2

0

มีข้อยกเว้นสำหรับการจัดการกับเงื่อนไขที่ไม่คาดคิด ณ รันไทม์เท่านั้น

การใช้ข้อยกเว้นแทนการตรวจสอบง่ายที่สามารถทำได้ในเวลารวบรวมจะชะลอการตรวจสอบจนถึงเวลาดำเนินการ จะเป็นการลดประสิทธิภาพของโปรแกรม

การขว้างข้อยกเว้นแทนที่จะใช้วิธีง่าย ๆถ้า ..การตรวจสอบความถูกต้องจะทำให้โค้ดซับซ้อนในการเขียนและบำรุงรักษา


-3

ความคิดเห็นของฉันเกี่ยวกับความเร็วในการยกเว้นเมื่อเปรียบเทียบกับการตรวจสอบข้อมูลโดยทางโปรแกรม

คลาสจำนวนมากมีตัวแปลงสตริงเป็นค่า (สแกนเนอร์ / parser) ซึ่งเป็นที่เคารพและรู้จักกันดีในห้องสมุดเช่นกัน;)

มักจะมีรูปแบบ

class Example {
public static Example Parse(String input) throws AnyRuntimeParsigException
...
}

ชื่อข้อยกเว้นเป็นเพียงตัวอย่างเท่านั้นโดยปกติจะไม่ถูกตรวจสอบ (รันไทม์) ดังนั้นการประกาศจะเป็นเพียงรูปภาพของฉัน

บางครั้งมีรูปแบบที่สอง:

public static Example Parse(String input, Example defaultValue)

ไม่เคยขว้าง

เมื่อโปรแกรมที่สองไม่พร้อมใช้งาน (หรือโปรแกรมเมอร์อ่านเอกสารน้อยกว่าและใช้ก่อนเท่านั้น) ให้เขียนโค้ดดังกล่าวด้วยนิพจน์ทั่วไป การแสดงออกปกติเท่ ๆ ถูกต้อง ฯลฯ

Xxxxx.regex(".....pattern", src);
if(ImTotallySure)
{
  Example v = Example.Parse(src);
}

ด้วยโปรแกรมเมอร์โค้ดนี้ไม่มีค่าใช้จ่ายยกเว้น แต่มีค่าใช้จ่ายสูงมากในการแสดงผลปกติเสมอเทียบกับค่าใช้จ่ายเล็กน้อยบางครั้ง

ฉันใช้เกือบทุกครั้งในบริบทดังกล่าว

try { parse } catch(ParsingException ) // concrete exception from javadoc
{
}

โดยไม่ต้องวิเคราะห์ stacktrace ฯลฯ ฉันเชื่อว่าหลังจากการบรรยายของคุณค่อนข้างเร็ว

อย่ากลัวข้อยกเว้น


-5

ทำไมข้อยกเว้นควรช้ากว่าผลตอบแทนปกติ

ตราบใดที่คุณไม่พิมพ์ stacktrace ไปยังเทอร์มินัลให้บันทึกลงในไฟล์หรือสิ่งที่คล้ายกัน catch-block จะไม่ทำงานมากกว่าบล็อกโค้ดอื่น ๆ ดังนั้นฉันไม่สามารถจินตนาการได้ว่าทำไม "โยน my_cool_error ()" ใหม่ควรจะช้า

เป็นคำถามที่ดีและฉันหวังว่าจะได้ข้อมูลเพิ่มเติมในหัวข้อนี้!


17
ข้อยกเว้นมีการรวบรวมข้อมูลเกี่ยวกับการติดตามสแต็กแม้ว่าจะไม่ได้ใช้จริง
Jon Skeet
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.