השפעות ביצועים של חריגים בג'אווה

1. סקירה כללית

בג'אווה, חריגים נחשבים בדרך כלל ליקרים ואין להשתמש בהם לבקרת זרימה. מדריך זה יוכיח שתפיסה זו נכונה ויצביע מה גורם לנושא הביצועים.

2. הגדרת סביבה

לפני כתיבת קוד להערכת עלות הביצועים, עלינו להגדיר סביבת ביצוע ביצועים.

2.1. רתמת Java Microbenchmark

מדידת תקורה חריגה אינה קלה כמו ביצוע שיטה בלולאה פשוטה ולשים לב לזמן הכולל.

הסיבה היא שמהדר בדיוק בזמן יכול להפריע ולייעל את הקוד. אופטימיזציה כזו עשויה לגרום לקוד לבצע ביצועים טובים יותר מאשר בפועל בסביבת ייצור. במילים אחרות, זה עשוי להניב תוצאות חיוביות כוזבות.

כדי ליצור סביבה מבוקרת שיכולה למתן אופטימיזציה של JVM, נשתמש ב- Java Microbenchmark Harness, או בקיצור JMH.

סעיפי המשנה הבאים יעברו בהגדרת סביבת benchmarking מבלי להיכנס לפרטים של JMH. למידע נוסף על כלי זה, עיין במדריך Microbenchmarking שלנו עם Java.

2.2. השגת חפצי JMH

כדי להשיג חפצים של JMH, הוסף את שתי התלות הללו ל- POM:

 org.openjdk.jmh jmh-core 1.21 org.openjdk.jmh jmh-generator-annprocess 1.21 

אנא עיין ב- Maven Central עבור הגרסאות העדכניות ביותר של JMH Core ו- JMH Annotation Processor.

2.3. כיתת מידוד

אנו זקוקים לשיעור כדי לקיים מידות:

@Fork (1) @Warmup (iterations = 2) @Measurement (iterations = 10) @BenchmarkMode (Mode.AverageTime) @OutputTimeUnit (TimeUnit.MILLISECONDS) class public ExceptionBenchmark {final final static int LIMIT = 10_000; // אמות מידה עבור לכאן}

בוא נעבור על ההערות של JMH המוצגות לעיל:

  • @מזלג: ציון מספר הפעמים שעל JMH להוליד תהליך חדש להפעלת אמות מידה. קבענו את הערך שלה ל -1 כדי ליצור תהליך אחד בלבד, ונמנע מלהמתין זמן רב מדי כדי לראות את התוצאה
  • @חימום: נשיאת פרמטרי חימום. ה איטרציות אלמנט להיות 2 פירושו ששתי הריצות הראשונות מתעלמות בעת חישוב התוצאה
  • @מדידה: נשיאת פרמטרים למדידה. An איטרציות ערך 10 מציין ש- JMH יבצע כל שיטה 10 פעמים
  • @BenchmarkMode: כך JHM צריך לאסוף תוצאות ביצוע. הערך זמן ממוצע מחייב את JMH לספור את הזמן הממוצע לשיטה הנדרשת להשלמת פעולותיה
  • @ OutputTimeUnit: מציין את יחידת זמן הפלט, שהיא המילישניות במקרה זה

בנוסף, יש שדה סטטי בתוך גוף הכיתה, כלומר לְהַגבִּיל. זהו מספר האיטרציות בכל גוף שיטה.

2.4. ביצוע מדדים

כדי לבצע אמות מידה, אנו זקוקים לא רָאשִׁי שיטה:

מעמד ציבורי MappingFrameworksPerformance {ציבורי ריק ריק סטטי (מחרוזת [] טענות) זורק חריג {org.openjdk.jmh.Main.main (טענות); }}

אנו יכולים לארוז את הפרויקט לקובץ JAR ולהריץ אותו בשורת הפקודה. פעולה זו כעת תייצר, כמובן, פלט ריק מכיוון שלא הוספנו שום שיטת אמת מידה.

לנוחיותנו, אנו יכולים להוסיף את תוסף maven-jar ל- POM. תוסף זה מאפשר לנו לבצע את רָאשִׁי שיטה בתוך IDE:

org.apache.maven.plugins maven-jar-plugin 3.2.0 com.baeldung.performancetests.MappingFrameworksPerformance 

הגרסה האחרונה של תוסף maven-jar ניתן למצוא כאן.

3. מדידת ביצועים

הגיע הזמן שיהיו לך כמה שיטות ביצוע ביצועים. כל אחת משיטות אלה חייבת לשאת את @ בנצ'מרק ביאור.

3.1. שיטה חוזרת בדרך כלל

נתחיל בשיטה שחוזרת כרגיל; כלומר שיטה שאינה זורקת חריג:

@Benchmark פומבי ריק doNotThrowException (Blackhole blackhole) {עבור (int i = 0; i <LIMIT; i ++) {blackhole.consume (אובייקט חדש ()); }}

ה חור שחור פרמטר מפנה למופע של חור שחור. זהו מחלקה JMH המסייעת במניעת חיסול קוד מת, אופטימיזציה שמהדר בדיוק בזמן עשוי לבצע.

המדד, במקרה זה, אינו זורק שום חריג. למעשה, נשתמש בו כנקודת התייחסות להערכת הביצועים של אלה שעושים חריגים.

ביצוע ה- רָאשִׁי השיטה תיתן לנו דוח:

מצב מידוד Cnt ציון שגיאות יחידות ExceptionBenchmark.doNotThrowException ממוצע 10 0.049 ± 0.006 ms / op

אין שום דבר מיוחד בתוצאה זו. זמן הביצוע הממוצע של אמת המידה הוא 0.049 אלפיות השנייה, וזה כשלעצמו די חסר משמעות.

3.2. יצירה וזריקה של חריג

הנה אמת מידה נוספת שזורקת ותופסת חריגים:

@Benchmark ציבורי בטל throwAndCatchException (Blackhole blackhole) {עבור (int i = 0; i <LIMIT; i ++) {נסה {throw Exception new (); } לתפוס (חריג e) {blackhole.consume (e); }}}

בואו נסתכל על הפלט:

מצב מידוד Cnt ציון שגיאות יחידות ExceptionBenchmark.doNotThrowException avgt 10 0.048 ± 0.003 ms / op ExceptionBenchmark.throwAndCatchException avgt 10 17.942 ± 0.846 ms / op

השינוי הקטן בזמן הביצוע של השיטה doNotThrowException לא חשוב. זו רק התנודה במצב של מערכת ההפעלה הבסיסית וה- JVM. המפתח העיקרי הוא זה הטלת חריג גורמת לשיטה לרוץ איטית פי מאות.

חלקי המשנה הבאים יגלו מה בדיוק מוביל להבדל כה דרמטי.

3.3. יצירת חריג בלי לזרוק אותו

במקום ליצור, לזרוק ולתפוס חריג, פשוט ניצור אותו:

@Benchmark חלל ציבורי createExceptionWithoutThrowingIt (Blackhole blackhole) {for (int i = 0; i <LIMIT; i ++) {blackhole.consume (Exception new ()); }}

עכשיו, בואו נבצע את שלושת המדדים שהכרזנו:

מצב מידוד Cnt ציון שגיאות יחידות ExceptionBenchmark.createExceptionWithoutTrowing זה ממוצע 10 17.601 ± 3.152 ms / op ExceptionBenchmark.doNotTrowException ממוצע 10 0.054 ± 0.014 ms / op ExceptionBenchmark.throwAndCatchException ממוצע 10 17.174 ± 0.474 ms / op

התוצאה עשויה להפתיע: זמן הביצוע של השיטה הראשונה והשלישית כמעט זהה, בעוד שזו השנייה קטנה בהרבה.

בשלב זה ברור כי ה לזרוק ו לתפוס ההצהרות עצמן זולות למדי. יצירת חריגים, לעומת זאת, מייצרת תקורות גבוהות.

3.4. השלכת חריג מבלי להוסיף את עקבות הערימה

בואו להבין מדוע לבנות חריג הוא הרבה יותר יקר מאשר לעשות חפץ רגיל:

@Benchmark @Fork (value = 1, jvmArgs = "-XX: -StackTraceInThrowable") זריקת חלל פומביתExceptionWithoutAddingStackTrace (Blackhole blackhole) {עבור (int i = 0; i <LIMIT; i ++) {נסה {לזרוק חריג חדש (); } לתפוס (חריג e) {blackhole.consume (e); }}}

ההבדל היחיד בין שיטה זו לזו שבסעיף קטן 3.2 הוא jvmArgs אֵלֵמֶנט. זה ערך -XX: -StackTraceInThrowable היא אפשרות JVM, השומרת על הוספת מעקב הערימה למעט.

בואו ננהל שוב את אמות המידה:

מצב מידוד Cnt ציון שגיאות יחידות ExceptionBenchmark.createExceptionWithoutTrowing זה ממוצע 10 17.874 ± 3.199 ms / op ExceptionBenchmark.doNotTrowException ממוצע 10 0.046 ± 0.003 ms / op ExceptionBenchmark.throwAndCatchException ממוצע 10 16.268 ± 0.239 ms / op חישוב

בכך שלא אכלסנו את החריג במעקב הערימה, צמצמנו את משך הביצוע ביותר מ- 100 פעמים. ככל הנראה, הליכה דרך הערימה והוספת מסגרותיה למעט יוצאות מהאיטיות שראינו.

3.5. השלכת יוצא מן הכלל ופירוק עקבות הערימה שלו

לבסוף, בואו נראה מה קורה אם נזרוק יוצא מן הכלל ונפרם את עקבות הערימה כשאנחנו תופסים אותו:

@Benchmark ציבור ריק ריק throwExceptionAndUnwindStackTrace (Blackhole blackhole) {עבור (int i = 0; i <LIMIT; i ++) {נסה {throw Exception new (); } לתפוס (חריג e) {blackhole.consume (e.getStackTrace ()); }}}

הנה התוצאה:

מצב מידוד Cnt ציון שגיאה יחידות ExceptionBenchmark.createExceptionWithoutTrowing זה ממוצע 10 16.605 ± 0.988 ms / op ExceptionBenchmark.doNotTrow זווית חשיפה ממוצעת 10 0.047 ± 0.006 ms / op ExceptionBenchmark.throwAndCatchException ממוצע 10 16.449 ± 0.304 ms / op חריג ExceptionBenchmark.throwExceptionWithoutAddingStackTrace avgt 10 1.185 ± 0.015 ms / op

רק על ידי ביטול מסלול הערימה, אנו רואים עלייה עצומה של פי 20 בערך משך הביצוע. במילים אחרות הביצועים גרועים בהרבה אם אנו מחלצים את מעקב הערימה מחריג, בנוסף לזריקתו.

4. מסקנה

במדריך זה ניתחנו את השפעות הביצוע של חריגים. באופן ספציפי, התברר כי עלות הביצועים היא בעיקר תוספת של עקבות הערימה למעט. אם עקבות הערימה הזו נפרשות לאחר מכן, התקורה הופכת גדולה הרבה יותר.

מכיוון שזריקה וטיפול בחריגים הם יקרים, אנחנו לא צריכים להשתמש בו לזרימת תוכניות רגילה. במקום זאת, כשמו כן הוא, להשתמש בחריגים רק במקרים חריגים.

קוד המקור השלם ניתן למצוא באתר GitHub.