ส่วนใดของการโยนข้อยกเว้นมีราคาแพง


256

ใน Java การใช้ throw / catch เป็นส่วนหนึ่งของตรรกะเมื่อไม่มีข้อผิดพลาดจริง ๆ แล้วเป็นความคิดที่ไม่ดี (ส่วนหนึ่ง) เนื่องจากการขว้างและจับข้อยกเว้นนั้นมีราคาแพงและการทำมันหลายครั้งในลูปมักจะช้ากว่าคนอื่น โครงสร้างการควบคุมที่ไม่เกี่ยวข้องกับการโยนข้อยกเว้น

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

กล่าวอีกนัยหนึ่งถ้าฉันทำ

Exception e = new Exception();

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

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

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


20
ฉันเชื่อว่ากำลังเติมและเติมการติดตามสแต็ก
Elliott Frisch

12
ตรวจสอบสิ่งนี้: stackoverflow.com/questions/16451777/…
Jorge

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

2
@Pshemo ผมไม่ได้วางแผนที่จริงทำเช่นนี้ในรหัสผมถามเกี่ยวกับผลการดำเนินงานและการใช้ความไร้สาระนี้เป็นตัวอย่างที่มันจะสร้างความแตกต่างหนึ่ง
Martin Carney

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

คำตอบ:


267

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

ตำนานเกี่ยวกับค่าใช้จ่ายที่สูงยกเว้นมาจากข้อเท็จจริงที่ว่าส่วนใหญ่ของการก่อสร้างโดยปริยายเรียกThrowable fillInStackTraceอย่างไรก็ตามมีคอนสตรัคเตอร์หนึ่งตัวที่สร้างThrowableโดยไม่มีการติดตามสแต็ก มันช่วยให้คุณสร้างสิ่งของที่รวดเร็วมากในการสร้างอินสแตนซ์ fillInStackTraceวิธีการสร้างข้อยกเว้นที่มีน้ำหนักเบาอีกประการหนึ่งคือการแทนที่


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

ถ้ามันถูกจับในวิธีเดียวกัน (หรือมากกว่านั้นในบริบทเดียวกันเนื่องจากบริบทสามารถมีหลายวิธีเนื่องจากการอินไลน์) ดังนั้นthrowจึงเป็นไปอย่างรวดเร็วและง่ายดายเหมือนgoto(แน่นอนหลังจากการรวบรวม JIT)

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


ผมสามารถยืนยันงบข้างต้นโดยมาตรฐานที่เหมาะสม แต่โชคดีที่ผมไม่ต้องทำเช่นนี้เนื่องจากทุกด้านอยู่แล้วครอบคลุมอย่างสมบูรณ์แบบในการโพสต์ของ HotSpot ประสิทธิภาพการทำงานของวิศวกร Alexey Shipilev นี้: ผลการดำเนินงานที่โดดเด่นของ Lil' ข้อยกเว้น


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

14
มันอาจจะคุ้มค่าที่จะคำนวณปริมาณค่าใช้จ่ายของข้อยกเว้น แม้แต่ในกรณีที่เลวร้ายที่สุดที่รายงานในบทความนี้ค่อนข้างละเอียดถี่ถ้วน (การขว้างและจับข้อยกเว้นแบบไดนามิกด้วยสแต็คสแตร็กที่มีการสอบถามจริง ๆ 1,000 เฟรมสแต็คลึก) ใช้เวลา 80 ไมโครวินาที ซึ่งอาจมีความสำคัญหากระบบของคุณต้องการประมวลผลข้อยกเว้นนับพันต่อวินาที แต่อย่างอื่นไม่น่าเป็นห่วง และนั่นเป็นกรณีที่เลวร้ายที่สุด หากกองการเรียงซ้อนของคุณน้อยหรือคุณไม่ต้องค้นหากองซ้อนเราสามารถประมวลผลข้อยกเว้นเกือบล้านต่อวินาที
meriton

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

2
มีส่วนหนึ่งที่ไม่ได้กล่าวถึงที่นี่: ค่าใช้จ่ายที่อาจเกิดขึ้นในการป้องกันการปรับให้เหมาะสมจากการใช้ ตัวอย่างสุดขั้วคือ JVM ที่ไม่อินไลน์เพื่อหลีกเลี่ยงร่องรอย "ยุ่งเหยิง" แต่ฉันเคยเห็น (ไมโคร) มาตรฐานที่การมีหรือไม่มีข้อยกเว้นจะทำให้หรือเพิ่มประสิทธิภาพใน C ++ ก่อน
Matthieu M.

3
@MatthieuM ข้อยกเว้นและบล็อคลอง / จับไม่ได้ป้องกัน JVM จากการทำอินไลน์ สำหรับวิธีการรวบรวมการติดตามสแต็กจริงจะถูกสร้างขึ้นใหม่จากตารางเฟรมสแต็กเสมือนที่เก็บไว้เป็นเมทาดาทา ฉันจำการเพิ่มประสิทธิภาพ JIT ที่ไม่สามารถใช้ร่วมกับลอง / จับได้ ลอง / จับโครงสร้างตัวเองไม่ได้เพิ่มอะไรไปยังรหัสวิธีการมันมีอยู่เป็นเพียงตารางข้อยกเว้นนอกเหนือจากรหัส
apangin

72

การดำเนินการครั้งแรกในตัวThrowableสร้างส่วนใหญ่คือการกรอกข้อมูลในการติดตามสแต็กซึ่งเป็นที่ที่ค่าใช้จ่ายส่วนใหญ่เป็น

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

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

Java เวอร์ชันปัจจุบันพยายามดำเนินการบางอย่างเพื่อปรับการสร้างสแต็กการติดตามให้เหมาะสม รหัสเนทีฟถูกเรียกใช้เพื่อเติมสแต็กการติดตามซึ่งบันทึกการติดตามในโครงสร้างเนทีฟน้ำหนักเบา สอดคล้อง Java StackTraceElementวัตถุที่ถูกสร้างขึ้นอย่างเฉื่อยชาจากบันทึกนี้เฉพาะเมื่อgetStackTrace(), printStackTrace()หรือวิธีการอื่น ๆ ที่จำเป็นต้องมีการติดตามจะเรียกว่า

หากคุณกำจัดการสร้างสแต็กการติดตามค่าใช้จ่ายหลักอื่นคือการคลายสแต็กระหว่างการโยนและการจับ ยิ่งมีการแทรกเฟรมน้อยลงก่อนที่จะเกิดข้อยกเว้น

ออกแบบโปรแกรมของคุณเพื่อให้เกิดข้อยกเว้นเฉพาะในกรณีพิเศษอย่างแท้จริงและการเพิ่มประสิทธิภาพเช่นนี้ยากที่จะพิสูจน์ได้


3
เชื่อมโยงไปยังคอนสตรัคเตอร์

25

มีการเขียนที่ดีเกี่ยวกับข้อยกเว้นที่นี่

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

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

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

Time to create million String objects: 41.41 (ms)
Time to create million JavaException objects with    stack: 608.89 (ms)
Time to create million JavaException objects without stack: 43.50 (ms)

ต่อไปนี้แสดงให้เห็นว่าต้องใช้เวลานานแค่ไหนในการกลับมาจากการขว้างที่ความลึกหนึ่งล้านครั้ง

|Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%)|
|   16|           1428|             243| 588 (%)|
|   15|           1763|             393| 449 (%)|
|   14|           1746|             390| 448 (%)|
|   13|           1703|             384| 443 (%)|
|   12|           1697|             391| 434 (%)|
|   11|           1707|             410| 416 (%)|
|   10|           1226|             197| 622 (%)|
|    9|           1242|             206| 603 (%)|
|    8|           1251|             207| 604 (%)|
|    7|           1213|             208| 583 (%)|
|    6|           1164|             206| 565 (%)|
|    5|           1134|             205| 553 (%)|
|    4|           1106|             203| 545 (%)|
|    3|           1043|             192| 543 (%)| 

ต่อไปนี้คือการทำให้เข้าใจง่ายกว่า ...

ถ้าเราใช้ความลึก 16 การเขียนสแต็คในการสร้างวัตถุนั้นใช้เวลาประมาณ ~ 40% ของเวลาการติดตามสแต็คที่เกิดขึ้นจริงสำหรับส่วนใหญ่นี้ ~ 93% ของการทำให้อินสแตนซ์ของวัตถุ JavaException เกิดขึ้นเนื่องจากการติดตามสแต็กที่กำลังดำเนินการ ซึ่งหมายความว่าการคลายสแต็กในกรณีนี้คืออีก 50% ของเวลา

เมื่อเราปิดบัญชีการสร้างวัตถุการติดตามสแต็กสำหรับเศษส่วนที่เล็กกว่ามากเช่น 20% และตอนนี้สแต็กคลี่คลายบัญชี 80% ของเวลา

ในทั้งสองกรณีการคลายสแต็กใช้เวลาส่วนใหญ่โดยรวม

public class JavaException extends Exception {
  JavaException(String reason, int mode) {
    super(reason, null, false, false);
  }
  JavaException(String reason) {
    super(reason);
  }

  public static void main(String[] args) {
    int iterations = 1000000;
    long create_time_with    = 0;
    long create_time_without = 0;
    long create_string = 0;
    for (int i = 0; i < iterations; i++) {
      long start = System.nanoTime();
      JavaException jex = new JavaException("testing");
      long stop  =  System.nanoTime();
      create_time_with += stop - start;

      start = System.nanoTime();
      JavaException jex2 = new JavaException("testing", 1);
      stop = System.nanoTime();
      create_time_without += stop - start;

      start = System.nanoTime();
      String str = new String("testing");
      stop = System.nanoTime();
      create_string += stop - start;

    }
    double interval_with    = ((double)create_time_with)/1000000;
    double interval_without = ((double)create_time_without)/1000000;
    double interval_string  = ((double)create_string)/1000000;

    System.out.printf("Time to create %d String objects: %.2f (ms)\n", iterations, interval_string);
    System.out.printf("Time to create %d JavaException objects with    stack: %.2f (ms)\n", iterations, interval_with);
    System.out.printf("Time to create %d JavaException objects without stack: %.2f (ms)\n", iterations, interval_without);

    JavaException jex = new JavaException("testing");
    int depth = 14;
    int i = depth;
    double[] with_stack    = new double[20];
    double[] without_stack = new double[20];

    for(; i > 0 ; --i) {
      without_stack[i] = jex.timerLoop(i, iterations, 0)/1000000;
      with_stack[i]    = jex.timerLoop(i, iterations, 1)/1000000;
    }
    i = depth;
    System.out.printf("|Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%%)|\n");
    for(; i > 0 ; --i) {
      double ratio = (with_stack[i] / (double) without_stack[i]) * 100;
      System.out.printf("|%5d| %14.0f| %15.0f| %2.0f (%%)| \n", i + 2, with_stack[i] , without_stack[i], ratio);
      //System.out.printf("%d\t%.2f (ms)\n", i, ratio);
    }
  }
 private int thrower(int i, int mode) throws JavaException {
    ExArg.time_start[i] = System.nanoTime();
    if(mode == 0) { throw new JavaException("without stack", 1); }
    throw new JavaException("with stack");
  }
  private int catcher1(int i, int mode) throws JavaException{
    return this.stack_of_calls(i, mode);
  }
  private long timerLoop(int depth, int iterations, int mode) {
    for (int i = 0; i < iterations; i++) {
      try {
        this.catcher1(depth, mode);
      } catch (JavaException e) {
        ExArg.time_accum[depth] += (System.nanoTime() - ExArg.time_start[depth]);
      }
    }
    //long stop = System.nanoTime();
    return ExArg.time_accum[depth];
  }

  private int bad_method14(int i, int mode) throws JavaException  {
    if(i > 0) { this.thrower(i, mode); }
    return i;
  }
  private int bad_method13(int i, int mode) throws JavaException  {
    if(i == 13) { this.thrower(i, mode); }
    return bad_method14(i,mode);
  }
  private int bad_method12(int i, int mode) throws JavaException{
    if(i == 12) { this.thrower(i, mode); }
    return bad_method13(i,mode);
  }
  private int bad_method11(int i, int mode) throws JavaException{
    if(i == 11) { this.thrower(i, mode); }
    return bad_method12(i,mode);
  }
  private int bad_method10(int i, int mode) throws JavaException{
    if(i == 10) { this.thrower(i, mode); }
    return bad_method11(i,mode);
  }
  private int bad_method9(int i, int mode) throws JavaException{
    if(i == 9) { this.thrower(i, mode); }
    return bad_method10(i,mode);
  }
  private int bad_method8(int i, int mode) throws JavaException{
    if(i == 8) { this.thrower(i, mode); }
    return bad_method9(i,mode);
  }
  private int bad_method7(int i, int mode) throws JavaException{
    if(i == 7) { this.thrower(i, mode); }
    return bad_method8(i,mode);
  }
  private int bad_method6(int i, int mode) throws JavaException{
    if(i == 6) { this.thrower(i, mode); }
    return bad_method7(i,mode);
  }
  private int bad_method5(int i, int mode) throws JavaException{
    if(i == 5) { this.thrower(i, mode); }
    return bad_method6(i,mode);
  }
  private int bad_method4(int i, int mode) throws JavaException{
    if(i == 4) { this.thrower(i, mode); }
    return bad_method5(i,mode);
  }
  protected int bad_method3(int i, int mode) throws JavaException{
    if(i == 3) { this.thrower(i, mode); }
    return bad_method4(i,mode);
  }
  private int bad_method2(int i, int mode) throws JavaException{
    if(i == 2) { this.thrower(i, mode); }
    return bad_method3(i,mode);
  }
  private int bad_method1(int i, int mode) throws JavaException{
    if(i == 1) { this.thrower(i, mode); }
    return bad_method2(i,mode);
  }
  private int stack_of_calls(int i, int mode) throws JavaException{
    if(i == 0) { this.thrower(i, mode); }
    return bad_method1(i,mode);
  }
}

class ExArg {
  public static long[] time_start;
  public static long[] time_accum;
  static {
     time_start = new long[20];
     time_accum = new long[20];
  };
}

สแต็กเฟรมในตัวอย่างนี้มีขนาดเล็กเมื่อเทียบกับสิ่งที่คุณมักจะพบ

คุณสามารถดู bytecode โดยใช้ javap

javap -c -v -constants JavaException.class

นั่นคือสำหรับวิธีที่ 4 ...

   protected int bad_method3(int, int) throws JavaException;
flags: ACC_PROTECTED
Code:
  stack=3, locals=3, args_size=3
     0: iload_1       
     1: iconst_3      
     2: if_icmpne     12
     5: aload_0       
     6: iload_1       
     7: iload_2       
     8: invokespecial #6                  // Method thrower:(II)I
    11: pop           
    12: aload_0       
    13: iload_1       
    14: iload_2       
    15: invokespecial #17                 // Method bad_method4:(II)I
    18: ireturn       
  LineNumberTable:
    line 63: 0
    line 64: 12
  StackMapTable: number_of_entries = 1
       frame_type = 12 /* same */

Exceptions:
  throws JavaException

13

การสร้างExceptionด้วยการnullติดตามแบบสแต็กนั้นใช้เวลานานเท่าthrowและและtry-catchบล็อกด้วยกัน อย่างไรก็ตามการกรอกกองติดตามจะใช้เวลาใน 5x เฉลี่ยอีกต่อไป

ฉันสร้างเกณฑ์มาตรฐานต่อไปนี้เพื่อสาธิตผลกระทบต่อประสิทธิภาพ ฉันเพิ่มลงใน-Djava.compiler=NONERun Configuration เพื่อปิดใช้งานการเพิ่มประสิทธิภาพคอมไพเลอร์ ในการวัดผลกระทบของการสร้างการติดตามสแต็กฉันได้ขยายExceptionคลาสเพื่อใช้ประโยชน์จาก Constructor ที่ไม่มีสแต็ก:

class NoStackException extends Exception{
    public NoStackException() {
        super("",null,false,false);
    }
}

รหัสมาตรฐานมีดังนี้:

public class ExceptionBenchmark {

    private static final int NUM_TRIES = 100000;

    public static void main(String[] args) {

        long throwCatchTime = 0, newExceptionTime = 0, newObjectTime = 0, noStackExceptionTime = 0;

        for (int i = 0; i < 30; i++) {
            throwCatchTime += throwCatchLoop();
            newExceptionTime += newExceptionLoop();
            newObjectTime += newObjectLoop();
            noStackExceptionTime += newNoStackExceptionLoop();
        }

        System.out.println("throwCatchTime = " + throwCatchTime / 30);
        System.out.println("newExceptionTime = " + newExceptionTime / 30);
        System.out.println("newStringTime = " + newObjectTime / 30);
        System.out.println("noStackExceptionTime = " + noStackExceptionTime / 30);

    }

    private static long throwCatchLoop() {
        Exception ex = new Exception(); //Instantiated here
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            try {
                throw ex; //repeatedly thrown
            } catch (Exception e) {

                // do nothing
            }
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long newExceptionLoop() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            Exception e = new Exception();
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long newObjectLoop() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            Object o = new Object();
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long newNoStackExceptionLoop() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            NoStackException e = new NoStackException();
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

}

เอาท์พุท:

throwCatchTime = 19
newExceptionTime = 77
newObjectTime = 3
noStackExceptionTime = 15

ซึ่งหมายความว่าการสร้างNoStackExceptionประมาณราคาแพงเป็นซ้ำ ๆ Exceptionขว้างปาเดียวกัน นอกจากนี้ยังแสดงให้เห็นว่าการสร้างExceptionและการเติมการติดตามสแต็กนั้นใช้เวลานานขึ้นประมาณ4x


1
คุณสามารถเพิ่มอีกหนึ่งกรณีที่คุณสร้างหนึ่งอินสแตนซ์ข้อยกเว้นก่อนเวลาเริ่มแล้วโยน + จับมันซ้ำ ๆ ในวง? นั่นจะแสดงค่าใช้จ่ายในการขว้างปา + จับ
Martin Carney

@MartinCarney ข้อเสนอแนะที่ดี! ฉันอัปเดตคำตอบเพื่อทำเช่นนั้น
Austin D

ฉันทำการปรับแต่งโค้ดทดสอบของคุณและดูเหมือนว่าคอมไพเลอร์กำลังทำการปรับให้เหมาะสมซึ่งทำให้เราได้รับหมายเลขที่ถูกต้อง
Martin Carney

@MartinCarney ฉันได้อัปเดตคำตอบสำหรับการเพิ่มประสิทธิภาพคอมไพเลอร์ส่วนลด
Austin D

FYI, คุณควรอ่านคำตอบของฉันจะเขียน micro-benchmark ที่ถูกต้องใน Java ได้อย่างไร? คำแนะนำ: นี่มันไม่ได้
Daniel Pryden

4

ส่วนนี้ของคำถาม ...

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

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

นี่คือเวลาที่ฉันได้โปรดอ่านข้อแม้หลังจากนี้ ...

|Depth| WriteStack(ms)| !WriteStack(ms)| Diff(%)|
|   16|            193|             251| 77 (%)| 
|   15|            390|             406| 96 (%)| 
|   14|            394|             401| 98 (%)| 
|   13|            381|             385| 99 (%)| 
|   12|            387|             370| 105 (%)| 
|   11|            368|             376| 98 (%)| 
|   10|            188|             192| 98 (%)| 
|    9|            193|             195| 99 (%)| 
|    8|            200|             188| 106 (%)| 
|    7|            187|             184| 102 (%)| 
|    6|            196|             200| 98 (%)| 
|    5|            197|             193| 102 (%)| 
|    4|            198|             190| 104 (%)| 
|    3|            193|             183| 105 (%)| 

แน่นอนว่าปัญหานี้คือการติดตามสแต็กของคุณตอนนี้ชี้ไปที่ที่คุณสร้างอินสแตนซ์ของวัตถุไม่ใช่ที่ที่มันถูกส่งออกมา


3

ใช้คำตอบของ @ AustinD เป็นจุดเริ่มต้นฉันได้ปรับแต่ง รหัสที่ด้านล่าง

นอกเหนือจากการเพิ่มเคสที่มีการยกเว้นอินสแตนซ์เดียวถูกโยนซ้ำ ๆ ฉันยังปิดการปรับแต่งคอมไพเลอร์เพื่อให้เราสามารถรับผลลัพธ์ประสิทธิภาพที่แม่นยำ ฉันเพิ่ม-Djava.compiler=NONEข้อโต้แย้ง VM ตามคำตอบนี้ (ใน eclipse ให้แก้ไข Run Run →อาร์กิวเมนต์เพื่อตั้งค่าอาร์กิวเมนต์ VM นี้)

ผลลัพธ์ที่ได้:

new Exception + throw/catch = 643.5
new Exception only          = 510.7
throw/catch only            = 115.2
new String (benchmark)      = 669.8

ดังนั้นการสร้างข้อยกเว้นมีค่าใช้จ่ายประมาณ 5x เท่าการขว้าง + จับมัน สมมติว่าคอมไพเลอร์ไม่ได้ปรับค่าใช้จ่ายให้เหมาะสม

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

new Exception + throw/catch = 382.6
new Exception only          = 379.5
throw/catch only            = 0.3
new String (benchmark)      = 15.6

รหัส:

public class ExceptionPerformanceTest {

    private static final int NUM_TRIES = 1000000;

    public static void main(String[] args) {

        double numIterations = 10;

        long exceptionPlusCatchTime = 0, excepTime = 0, strTime = 0, throwTime = 0;

        for (int i = 0; i < numIterations; i++) {
            exceptionPlusCatchTime += exceptionPlusCatchBlock();
            excepTime += createException();
            throwTime += catchBlock();
            strTime += createString();
        }

        System.out.println("new Exception + throw/catch = " + exceptionPlusCatchTime / numIterations);
        System.out.println("new Exception only          = " + excepTime / numIterations);
        System.out.println("throw/catch only            = " + throwTime / numIterations);
        System.out.println("new String (benchmark)      = " + strTime / numIterations);

    }

    private static long exceptionPlusCatchBlock() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            try {
                throw new Exception();
            } catch (Exception e) {
                // do nothing
            }
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long createException() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            Exception e = new Exception();
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long createString() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            Object o = new String("" + i);
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }

    private static long catchBlock() {
        Exception ex = new Exception(); //Instantiated here
        long start = System.currentTimeMillis();
        for (int i = 0; i < NUM_TRIES; i++) {
            try {
                throw ex; //repeatedly thrown
            } catch (Exception e) {
                // do nothing
            }
        }
        long stop = System.currentTimeMillis();
        return stop - start;
    }
}

ปิดใช้งานการเพิ่มประสิทธิภาพ = เทคนิคที่ยอดเยี่ยม! ฉันจะแก้ไขคำตอบดั้งเดิมของฉันเพื่อไม่ให้เข้าใจผิดใคร
Austin D

3
การปิดใช้งานการปรับให้เหมาะสมนั้นไม่ได้ดีไปกว่าการเขียนเกณฑ์มาตรฐานที่สมบูรณ์เนื่องจากโหมดตีความที่บริสุทธิ์นั้นไม่เกี่ยวข้องกับประสิทธิภาพการทำงานจริง พลังของ JVM คือคอมไพเลอร์ JIT ดังนั้นอะไรคือจุดของการวัดบางสิ่งที่ไม่สะท้อนถึงการใช้งานจริง
apangin

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