מיתרים קומפקטיים ב- Java 9

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

מיתרים ב- Java מיוצגים באופן פנימי על ידי a לְהַשְׁחִיר[] המכיל את הדמויות של חוּט. וגם, כל אחד לְהַשְׁחִיר מורכב משני בתים בגלל ג'אווה משתמשת באופן פנימי ב- UTF-16.

למשל, אם א חוּט מכילה מילה בשפה האנגלית, 8 הביטים המובילים יהיו כולם 0 לכל לְהַשְׁחִיר, בתור ASCII ניתן לייצג באמצעות בת אחד.

תווים רבים דורשים 16 ביטים כדי לייצג אותם אך סטטיסטית רובם דורשים 8 ביטים בלבד - ייצוג תווים LATIN-1. לכן, יש היקף לשיפור צריכת הזיכרון והביצועים.

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

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

2. דחוס חוּט - ג'אווה 6

עדכון JDK 6 21 Performance Release, הציג אפשרות VM חדשה:

-XX: + UseCompressedStrings

כאשר אפשרות זו מופעלת, מיתרים מאוחסנים כ- בתים [], במקום char [] - וכך, חוסך זיכרון רב. עם זאת, אפשרות זו הוסרה בסופו של דבר ב- JDK 7, בעיקר משום שהיו לה השלכות ביצועיות לא מכוונות.

3. קומפקטי חוּט - ג'אווה 9

ג'אווה 9 הביאה את המושג קומפקט מיתרים תוֹאַר רִאשׁוֹןck.

זה אומר ש בכל פעם שאנחנו יוצרים a חוּט אם כל הדמויות של חוּט ניתן לייצג באמצעות בתים - ייצוג LATIN-1, ישמש מערך בתים באופן פנימי, כזה שניתן בית אחד לדמות אחת.

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

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

עכשיו, השאלה היא - איך כל זה חוּט פעולות פעולות? כיצד היא תבחין בין ייצוג ה- LATIN-1 ו- UTF-16?

ובכן, כדי להתמודד עם נושא זה, נעשה שינוי נוסף ביישום הפנימי של ה- חוּט. יש לנו שדה אחרון קוֹדַאִי, המשמר מידע זה.

3.1. חוּט יישום ב- Java 9

עד עכשיו, חוּט אוחסן כ- לְהַשְׁחִיר[]:

ערך תו סופי פרטי [];

מעכשיו זה יהיה בתים []:

ערך בתים פרטיים סופיים [];

המשתנה קוֹדַאִי:

קודן בתים סופיים פרטיים;

איפה ה קוֹדַאִי יכול להיות:

בתים סופיים סטטיים LATIN1 = 0; בתים סופיים סטטיים UTF16 = 1;

רוב ה חוּט פעולות בודקות כעת את הקודן ומשלחות ליישום הספציפי:

public int indexOf (int ch, int fromIndex) {return isLatin1 ()? StringLatin1.indexOf (value, ch, fromIndex): StringUTF16.indexOf (value, ch, fromIndex); } בוליאני פרטי isLatin1 () {return COMPACT_STRINGS && קודן == LATIN1; } 

עם כל המידע ש- JVM צריך להיות מוכן וזמין, ה- CompactString אפשרות ה- VM מופעלת כברירת מחדל. כדי להשבית אותו, נוכל להשתמש ב:

+ XX: -CompactStrings

3.2. אֵיך קוֹדַאִי עובד

בג'אווה 9 חוּט יישום בכיתה, האורך מחושב כ:

אורך int ציבורי () {קוד value.length >> קודן; }

אם ה חוּט מכיל רק LATIN-1, הערך של ה- קוֹדַאִי יהיה 0 כך שאורכו של חוּט יהיה זהה לאורך מערך הבתים.

במקרים אחרים, אם חוּט הוא בייצוג UTF-16, הערך של קוֹדַאִי יהיה 1, ומכאן שהאורך יהיה חצי מגודל מערך הבתים בפועל.

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

4. קומפקטי מיתרים לעומת דחוס מיתרים

במקרה של JDK 6 דחוס מיתרים, בעיה מרכזית שעומדת בפניו הייתה שה- חוּט קונסטרוקטור התקבל בלבד לְהַשְׁחִיר[] כוויכוח. בנוסף לכך, רבים חוּט הפעולות היו תלויות לְהַשְׁחִיר[] ייצוג ולא מערך בתים. בשל כך היה צורך לבצע פריקה רבה שהשפיעה על הביצועים.

ואילו במקרה של קומפקט חוּט, שמירה על "קודן" בשדה הנוסף יכולה גם להגדיל את התקורה. כדי להקל על עלות ה- קוֹדַאִי והפריקה של בתיםs ל לְהַשְׁחִירs (במקרה של ייצוג UTF-16), חלק מהשיטות אינן מכוונות וקוד ASM שנוצר על ידי מהדר JIT שופר גם כן.

שינוי זה הביא לכמה תוצאות נגד אינטואיטיביות. ה- LATIN-1 indexOf (מחרוזת) מכנה שיטה מהותית, ואילו indexOf (char) לא. במקרה של UTF-16, שתי השיטות הללו מכנות שיטה מהותית. בעיה זו משפיעה רק על ה- LATIN-1 חוּט ויתוקן במהדורות עתידיות.

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

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

4.1. הבדל בביצועים

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

זמן התחלה ארוך = System.currentTimeMillis (); מחרוזות רשימה = IntStream.rangeClosed (1, 10_000_000) .mapToObj (מספר שלם :: toString) .collect (toList ()); longTimeTime = System.currentTimeMillis () - startTime; System.out.println ("נוצר" + מחרוזות.גודל () + "מחרוזות" + totalTime + "ms."); startTime = System.currentTimeMillis (); מחרוזת נספח = (מחרוזת) strings.stream () .limit (100_000) .פחת ("", (l, r) -> l.toString () + r.toString ()); totalTime = System.currentTimeMillis () - startTime; System.out.println ("נוצר מחרוזת אורך" + appended.length () + "ב-" + totalTime + "ms.");

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

נוצר 10000000 מחרוזות ב 854 אלפיות השנייה. נוצר מחרוזת באורך 488895 ב 5130 ms.

באופן דומה, אם נפעיל אותו על ידי השבתת המיתרים הקומפקטיים באמצעות: -XX: -CompactStrings אפשרות, הפלט הוא:

נוצר 10000000 מחרוזות ב 936 אלפיות השנייה. נוצר מחרוזת באורך 488895 ב 9727 ms.

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

5. מסקנה

במדריך זה ראינו את הניסיונות לייעל את הביצועים ואת צריכת הזיכרון ב- JVM - באמצעות אחסון חוּטבדרך זיכרון יעילה.

כמו תמיד, הקוד כולו זמין ב- Github.


$config[zx-auto] not found$config[zx-overlay] not found