Microbenchmarking עם Java

1. הקדמה

מאמר מהיר זה מתמקד ב- JMH (Java Microbenchmark Harness). ראשית, אנו מכירים את ה- API ולומדים את יסודותיו. אז נראה כמה שיטות עבודה מומלצות שעלינו לקחת בחשבון בעת ​​כתיבת סימני מיקרו-בנצ'ים.

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

2. תחילת העבודה

כדי להתחיל, אנו יכולים להמשיך לעבוד עם Java 8 ופשוט להגדיר את התלות:

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

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

לאחר מכן, צור אמת מידה פשוטה באמצעות @ בנצ'מרק ביאור (בכל סוג ציבורי):

@Benchmark פנוי init בטל () {// אל תעשה כלום}

לאחר מכן אנו מוסיפים את המחלקה הראשית שמתחילה את תהליך המידוד:

Class class BenchmarkRunner {public static void main (String [] args) זורק Exception {org.openjdk.jmh.Main.main (args); }}

עכשיו רץ BenchmarkRunner יבצע את אמת המידה הטעונה שלנו לטענת. לאחר סיום הריצה, מוצגת טבלת סיכום:

# הפעל הושלם. זמן כולל: 00:06:45 יחידות מידה שוויון ציון יחידות ציון BenchMark.init thrpt 200 3099210741.962 ± 17510507.589 ops / s

3. סוגי מידודים

JMH תומך בכמה מדדים אפשריים: תפוקה,זמן ממוצע,SampleTime, ו SingleShotTime. ניתן להגדיר את אלה באמצעות @BenchmarkMode ביאור:

@Benchmark @BenchmarkMode (Mode.AverageTime) init חלל ציבורי () {// אל תעשה כלום}

לטבלה המתקבלת תהיה מדד זמן ממוצע (במקום תפוקה):

# הפעל הושלם. זמן כולל: 00:00:40 יחידות שגיאה במצב ציון שווי ציון BenchMark.init avgt 20 ≈ 10⁻⁹ s / op

4. קביעת תצורה של חימום וביצוע

באמצעות @מזלג ביאור, אנו יכולים להגדיר כיצד מתרחשת ביצוע הביצועים: ערך הפרמטר שולט כמה פעמים ביצוע הביצועים, וה- חימום פרמטר שולט כמה פעמים אמת מידה תתייבש לפני איסוף התוצאות, למשל:

@Benchmark @Fork (value = 1, warmups = 2) @BenchmarkMode (Mode.Troughput) init ריק ריק () {// אל תעשה כלום}

זה מורה ל- JMH להריץ שני מזלגות חימום ולזרוק תוצאות לפני שעוברים למבחן ביצועים אמיתי בזמן אמת.

וגם ה @חימום ניתן להשתמש בהערה כדי לשלוט במספר חזרות החימום. לדוגמה, @Warmup (איטרציות = 5) אומר ל- JMH שחמישה חזרות חימום יספיקו, לעומת ברירת המחדל 20.

5. מדינה

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

אנו יכולים לחקור את השפעת הביצועים באמצעות מדינה לְהִתְנַגֵד:

@State (Scope.Benchmark) ExhibitionPlan {@Param ({"100", "200", "300", "500", "1000"}) אינטרס ציבורי בכיתה ציבורית; רחש האשר הציבורי 3; סיסמת מחרוזת ציבורית = "4v3rys3kur3p455w0rd"; @ Setup (Level.Invocation) setUp public public ריק () {murmur3 = Hashing.murmur3_128 (). NewHasher (); }}

שיטת המבחן שלנו תיראה כך:

@Fork (value = 1, warmups = 1) @Benchmark @BenchmarkMode (Mode.Troughput) ספסל חלל ציבורי Murmur3_128 (תוכנית ExecutionPlan) {עבור (int i = plan.iterations; i> 0; i--) {plan.murmur3. putString (plan.password, Charset.defaultCharset ()); } plan.murmur3.hash (); }

הנה, השדה איטרציות יאוכלס בערכים מתאימים מה- @ פארם ביאור על ידי ה- JMH כאשר הוא מועבר לשיטת אמת המידה. ה @להכין מתבצעת שיטה מסומנת לפני כל הפעלה של אמת מידה ויוצרת שיטה חדשה האשר הבטחת בידוד.

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

# הפעל הושלם. זמן כולל: 00:06:47 מידוד (חזרות) מצב Cnt ציון שגיאות יחידות BenchMark.benchMurmur3_128 100 thrpt 20 92463.622 ± 1672.227 ops / s BenchMark.benchMurmur3_128 200 thrpt 20 39737.532 ± 5294.200 ops / s BenchMark.benchMurpt3128 6128 ops / s BenchMark.benchMurmur3_128 500 thrpt 20 18315.211 ± 222.534 ops / s BenchMark.benchMurmur3_128 1000 thrpt 20 8960.008 ± 658.524 ops / s

6. חיסול קוד מת

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

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

@Benchmark @OutputTimeUnit (TimeUnit.NANOSECONDS) @BenchmarkMode (Mode.AverageTime) חלל ציבורי doNothing () {} @Benchmark @OutputTimeUnit (TimeUnit.NANOSECONDS) @BenchmarkMode (Mode.AverageTime) ציבורי ריק אובייקט () אובייקט (חדש) {אובייקט חדש }

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

יחידות שגיאה של ציון מצב ציון BenchMark.do אין שום ערך 40 0.609 ± 0.006 ns / op BenchMark.objectCreation avgt 40 0.613 ± 0.007 ns / op

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

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

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

@Benchmark @OutputTimeUnit (TimeUnit.NANOSECONDS) @BenchmarkMode (Mode.AverageTime) עמודי אובייקט ציבוריים עמודי אובייקט () {להחזיר אובייקט חדש (); }

כמו כן, אנו יכולים לתת ל חור שחור לצרוך את זה:

@Benchmark @OutputTimeUnit (TimeUnit.NANOSECONDS) @BenchmarkMode (Mode.AverageTime) ציבורי ריק blackHole (Blackhole blackhole) {blackhole.consume (אובייקט חדש ()); }

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

יחידות BenchMark.blackHole avgt 20 4.126 ± 0.173 ns / op

7. קיפול מתמיד

הבה נבחן דוגמה נוספת:

@Benchmark ציבורי כפול מקופל () {int x = 8; החזר Math.log (x); }

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

@Benchmark ציבורי כפול מקופל () {לחזור 2.0794415416798357; }

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

על מנת למנוע קיפול מתמיד, נוכל להכיל את המצב הקבוע בתוך אובייקט מצב:

@State (Scope.Benchmark) מחלקה סטטית ציבורית יומן {public int x = 8; } @Benchmark יומן כפול ציבורי (קלט יומן) {החזר Math.log (input.x); }

אם אנו מריצים את המדדים האלו זה נגד זה:

יחידות שגיאה במצב ציון Cnt ציון BenchMark.foldedLog thrpt 20 449313097.433 ± 11850214.900 ops / s BenchMark.log thrpt 20 35317997.064 ± 604370.461 ops / s

ככל הנראה, עֵץ מדד עושה עבודה רצינית בהשוואה ל מקופל, שזה הגיוני.

8. מסקנה

מדריך זה התמקד והציג לראווה את רתמת המייצ'ר של ג'אווה.

כמו תמיד, ניתן למצוא דוגמאות קוד ב- GitHub.