מאמר טכני

שמירת תת-קבוצות גופנים במטמון על דיסק עם HotPDF בדלפי

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

תרשים זרימה של טיפול ברשומות מטמון פגומות ב-HotPDF: אימות לפני שימוש, דחייה והסרה לפני כל זרם PDF, נפילה בחזרה אל חיתוך גופן בזיכרון
רשומות פגומות נכשלות בפתיחות — המסמך עדיין נוצר, ופלט PDF/A שומר על המטמון כי הרשומות משמרות את מיפוי ה-CID

Subsetting יקר מסיבה. בניית תת-קבוצה פירושה ללכת על סגירת הגליף, לכתוב מחדש loca ו-glyf, לבנות מחדש cmap ו-hmtx, ולפלוט מיפוי CID שה-PDF יכול לכתוב אליו. עבור מסמך אחד העלות הזו נעלמת לתוך הרעש. עבור שרת דוחות שמייצר מסמכים בלולאה, זוהי לעיתים קרובות בלוק ה-CPU הגדול ביותר בהרצה

מה הופך פגיעה במטמון לאפשרית

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

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

var
  Pdf: THotPDF;
  Info: THPDFFontSubsetCacheInfo;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.EnableFontSubsetting := True;
    Pdf.FontSubsetCacheFolder := 'C:\ProgramData\Reports\fontcache';
    Pdf.FontSubsetCacheMaxBytes := 64 * 1024 * 1024;   // 64 MiB, ברירת המחדל היא 256
    // ... הפקת האצווה ...
    Info := Pdf.GetFontSubsetCacheInfo;
    LogFmt('subset cache: %d hits, %d misses, %d bytes in %d files',
      [Info.HitCount, Info.MissCount, Info.CurrentBytes, Info.FileCount]);
  finally
    Pdf.Free;
  end;
end;

איך אתה יודע שהמטמון עושה משהו?

GetFontSubsetCacheInfo מחזיר תשעה מונים, והיחס בין השניים הראשונים עונה על השאלה ישירות. HitCount ו-MissCount נותנים את שיעור הפגיעה. WriteCount ו-EvictionCount מראים האם רשומות שורדות מספיק זמן לשימוש חוזר או שהן נדחקות החוצה על ידי תקציב שקטן מדי. CurrentBytes ו-FileCount מדווחים מה נמצא על הדיסק כעת

שלושת הנותרים הם אלה שכדאי להזעיק עליהם התראה. CorruptCount סופר רשומות שנכשלו בתיקוף והוסרו — מעט לאחר כיבוי לא-תקין הם רגילים, זרם יציב פירושו שהאחסון אינו אמין. RejectedCount סופר רשומות שסורבו לפני שימוש. WriteFailureCount סופר רשומות שלא ניתן היה לכתוב כלל, מה שבדרך כלל אומר בעיית הרשאות על התיקייה ולא דבר על גופנים. אף אחד משלושת אלה אינו עוצר ייצור מסמך, שזה בדיוק למה צריך להסתכל עליהם: מטמון שלעולם לא כותב בשקט נראה זהה מבחוץ למטמון שעובד, למעט חשבון ה-CPU

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

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

FontSubsetCacheMaxBytes ברירת המחדל היא 268435456 בייטים, כלומר 256 MiB, וניתן להקטינה בזמן ריצה. הקטנה מפעילה פינוי least-recently-used מיידי במקום לחכות לכתיבה הבאה, כך ששירות שמגיב ללחץ דיסק יכול לשחרר מקום ברגע שהוא מחליט על כך, לא בנקודה מאוחרת כלשהי שאינו שולט בה

פאנל HotPDF של תשעת מוני GetFontSubsetCacheInfo המקובצים לקצב פגיעה, הישרדות וחיסכון, עם שלוש קריאות המצביעות על ההגדרה שיש לשנות
היחס בין HitCount ל-MissCount עונה האם המטמון עושה משהו, והמונים האחרים אומרים מדוע לא

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

מה המטמון עושה כשרשומה פגומה

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

כתיבות אטומיות, כך שקורא לעולם אינו צופה ברשומה חצי-כתובה, וקריסה באמצע כתיבה משאירה את המטמון עקבי במקום מורעל. רשומות תת-קבוצה קומפקטיות שומרות על נתוני מיפוי-מחדש של CID שמילוני גופן של PDF/A דורשים, כך שתת-קבוצה במטמון היא עדיין תת-קבוצה תואמת — פלט ארכיוני אינו צריך לעקוף את המטמון כדי להישאר תקף

// איפוס שכבת הדיסק לאחר שדרוג גופן או שינוי סכימה
Pdf.ClearFontSubsetCache;

// או העבירו אותה למקום בר-כתיבה ותנו לתקציב לחול מיידית
Pdf.SetFontSubsetCacheFolder('D:\cache\fonts');

היכן למקם את התיקייה בפריסה אמיתית

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

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

מתי להגיע למשהו אחר

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

HotPDF היא רכיב PDF VCL מקורי לדלפי ו-C++Builder, ומטמון תת-הקבוצה הוא חלק מהספרייה במקום שירות תוסף, כך ששרת דוחות מקבל אותו על ידי הגדרת נתיב תיקייה אחד — ראה את דף רכיב HotPDF לרשימת התכונות המלאה של גופנים וביצועים