Τεχνικό Άρθρο

Αποθήκευση υποσυνόλων γραμματοσειρών σε δίσκο με HotPDF σε Delphi

Το HotPDF μπορεί να κρατά υποσύνολα γραμματοσειρών TrueType και OpenType σε δίσκο και να τα επαναχρησιμοποιεί μεταξύ εγγράφων και μεταξύ εκτελέσεων διεργασίας, ώστε μια παρτίδα που αποδίδει δέκα χιλιάδες καταστάσεις με τις ίδιες τρεις γραμματοσειρές να κάνει subset αυτών των γραμματοσειρών μία φορά αντί για δέκα χιλιάδες. Η κρυφή μνήμη ρυθμίζεται με δύο ιδιότητες, επιθεωρείται με μία εγγραφή, και είναι ασφαλές να αφεθεί ανοιχτή: μια αποτυχία της κρυφής μνήμης επιστρέφει στο κανονικό subset στη μνήμη και δεν σταματά ποτέ ένα έγγραφο να παραχθεί

Το subsetting είναι ακριβό για έναν λόγο. Η κατασκευή ενός υποσυνόλου σημαίνει να διασχίσετε το κλείσιμο γλυφών, να ξαναγράψετε τα loca και glyf, να ξαναχτίσετε τα cmap και hmtx, και να εκπέμψετε μια αντιστοίχιση CID που μπορεί να διευθύνει το PDF. Για ένα έγγραφο αυτό το κόστος χάνεται μέσα στον θόρυβο. Για έναν διακομιστή αναφορών που παράγει έγγραφα μέσα σε βρόχο, είναι συχνά η μεγαλύτερη μεμονωμένη μονάδα χρόνου CPU στην εκτέλεση

Τι κάνει δυνατή μια εύρεση στην κρυφή μνήμη

Τέσσερα πράγματα πρέπει να ταιριάζουν: το περιεχόμενο της γραμματοσειράς, το σύνολο των χρησιμοποιημένων γλυφών, η λειτουργία subset, και το σχήμα της κρυφής μνήμης. Ασταχείτε σε οποιοδήποτε από αυτά και το HotPDF κάνει subset από την αρχή, επειδή ένα υποσύνολο είναι επαναχρησιμοποιήσιμο μόνο όταν θα ήταν παντελώς ίδιο σε bytes

Το σύνολο γλυφών είναι η συνθήκη που εκπλήσσει τους ανθρώπους. Δύο τιμολόγια που διαφέρουν κατά ένα όνομα πελάτη χρησιμοποιούν διαφορετικά σύνολα γλυφών, και συνεπώς παράγουν διαφορετικά υποσύνολα και διαφορετικές καταχωρήσεις κρυφής μνήμης. Η κρυφή μνήμη αποδίδει όταν τα έγγραφα μοιράζονται ένα ρεπερτόριο γλυφών — καταστάσεις από ένα σταθερό πρότυπο, φόρμες των οποίων τα μεταβλητά δεδομένα είναι αριθμητικά, κατάλογοι αντλημένοι από μία βάση δεδομένων προϊόντων — και δεν αποδίδει τίποτα όταν κάθε έγγραφο σχεδιάζει ένα διαφορετικό κομμάτι μιας μεγάλης 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, default is 256
    // ... generate the batch ...
    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 δείχνουν αν οι καταχωρήσεις επιζούν αρκετά για να επαναχρησιμοποιηθούν ή αποσύρονται από ένα budget πολύ μικρό. Οι CurrentBytes και FileCount αναφέρουν τι υπάρχει στον δίσκο αυτή τη στιγμή

Οι υπόλοιπες τρεις είναι αυτές που αξίζει να ειδοποιείτε. Η CorruptCount μετρά καταχωρήσεις που απέτυχαν σε επικύρωση και αφαιρέθηκαν — μερικές μετά από μια μη καθαρή διακοπή είναι φυσιολογικές, μια σταθερή ροή σημαίνει ότι ο αποθηκευτικός χώρος είναι αναξιόπιστος. Η RejectedCount μετρά καταχωρήσεις που απορρίφθηκαν πριν τη χρήση. Η WriteFailureCount μετρά καταχωρήσεις που δεν μπόρεσαν να γραφτούν καθόλου, που συνήθως σημαίνει πρόβλημα δικαιωμάτων στον φάκελο παρά κάτι για γραμματοσειρές. Καμία από αυτές τις τρεις δεν σταματά την παραγωγή εγγράφων, που είναι ακριβώς γιατί πρέπει να τις κοιτάτε: μια κρυφή μνήμη που σιωπηλά δεν γράφει ποτέ φαίνεται ίδια από έξω με μια κρυφή μνήμη που λειτουργεί, εκτός από τον λογαριασμό CPU

Αποβολή, budgets και η στιγμή που μικραίνετε ένα

Η FontSubsetCacheMaxBytes έχει προεπιλογή 268435456 bytes, δηλαδή 256 MiB, και μπορεί να μειωθεί σε runtime. Η μείωσή της ενεργοποιεί άμεση αποβολή least-recently-used αντί να περιμένει την επόμενη εγγραφή, οπότε μια υπηρεσία που αντιδρά σε πίεση δίσκου μπορεί να ελευθερώσει χώρο τη στιγμή που αποφασίζει, όχι σε κάποιο μεταγενέστερο σημείο που δεν ελέγχει

Ορίζοντας την FontSubsetCacheFolder σε κενή συμβολοσειρά απενεργοποιείτε το επίπεδο δίσκου χωρίς να καθαρίσετε ό,τι ήδη αποθηκεύτηκε, και χωρίς να αλλάξετε ούτε ένα byte της εξόδου γραμματοσειράς. Αυτή είναι η ιδιότητα που πρέπει να καταφύγετε όταν θέλετε να απομονώσετε την κρυφή μνήμη κατά την αντιμετώπιση προβλημάτων: κλείστε την, τρέξτε την ίδια παρτίδα, και συγκρίνετε τα παραχθέντα PDF. Πρέπει να είναι πανομοιότυπα, επειδή η κρυφή μνήμη αποθηκεύει ένα αποτέλεσμα, όχι μια πολιτική

Τι κάνει η κρυφή μνήμη όταν μια καταχώριση είναι φθαρμένη

Την αφαιρεί και κάνει subset κανονικά. Κακοσχηματισμένες ή περικομμένες καταχωρήσεις απορρίπτονται πριν το υποσύνολο μπορέσει να φτάσει σε PDF stream, που είναι το κομμάτι του σχεδιασμού με τη μεγαλύτερη σημασία: μια φθαρμένη καταχώριση κρυφής μνήμης που θα έφτανε σε ένα έγγραφο θα παρήγαγε ένα PDF με σπασμένο πρόγραμμα γραμματοσειράς, και αυτή η αποτυχία θα αναδυόταν μακριά από την αιτία της — σε έναν αναγνώστη, σε ένα μηχάνημα πελάτη, εβδομάδες αργότερα

Οι εγγραφές είναι ατομικές, οπότε ένας αναγνώστης δεν παρατηρεί ποτέ μισογραμμένη καταχώριση, και μια κατάρρευση ενδιάμεσα στην εγγραφή αφήνει την κρυφή μνήμη συνεπή παρά δηλητηριασμένη. Οι συμπαγείς καταχωρήσεις υποσυνόλων διατηρούν τα δεδομένα επανααντιστοίχισης CID που απαιτούν τα λεξικά γραμματοσειρών PDF/A, οπότε ένα υποσύνολο σε κρυφή μνήμη παραμένει συμμορφούμενο υποσύνολο — η αρχειακή έξοδος δεν χρειάζεται να παρακάμψει την κρυφή μνήμη για να μείνει έγκυρη

// Reset the disk tier after a font upgrade or a schema change
Pdf.ClearFontSubsetCache;

// Or move it somewhere writable and let the budget apply immediately
Pdf.SetFontSubsetCacheFolder('D:\cache\fonts');

Πού να βάλετε τον φάκελο σε μια πραγματική ανάπτυξη

Τρεις ιδιότητες το αποφασίζουν: ο φάκελος πρέπει να είναι εγγράψιμος από τον λογαριασμό με τον οποίο τρέχει η υπηρεσία, πρέπει να κάθεται σε τοπικό αποθηκευτικό μέσο παρά σε κοινόχρηστο δίκτυο, και δεν πρέπει να είναι μέσα σε έναν κατάλογο που ένα βήμα ανάπτυξης καθαρίζει. Μια κρυφή μνήμη σε κοινόχρηστο δίκτυο μετατρέπει κάθε αστοχία σε ένα round trip και κάθε εύρεση σε δύο· μια κρυφή μνήμη κάτω από έναν φάκελο εφαρμογής που ο installer δημιουργεί εκ νέου είναι μια κρυφή μνήμη που ξεκινά κρύα μετά από κάθε ενημέρωση

Για υπηρεσίες πολλαπλών στιγμιοτύπων, δώστε σε κάθε στιγμιότυπο τον δικό του φάκελο εκτός αν έχετε επιβεβαιώσει ότι ο αποθηκευτικός χώρος χειρίζεται την ταυτόχρονη ατομική αντικατάσταση όπως περιμένετε. Το κόστος μιας διπλότυπης καταχώρισης είναι ένα επιπλέον πέρασμα subsetting· το κόστος της αποσφαλμάτωσης ενός ανταγωνισμού κοινής κρυφής μνήμης είναι ένα απόγευμα

Πότε να καταφύγετε σε κάτι άλλο

Η κρυφή μνήμη μειώνει την επαναλαμβανόμενη εργασία. Δεν μειώνει την εργασία του πρώτου εγγράφου, και δεν βοηθά μια φόρτωση εργασίας της οποίας τα σύνολα γλυφών δεν επαναλαμβάνονται ποτέ. Αν η έξοδός σας κυριαρχείται από μια τεράστια CJK γραμματοσειρά που χρησιμοποιείται σε μη προβλέψιμο κείμενο, ο πιο αποτελεσματικός μοχλός είναι το κλείσιμο subsetting καθαυτό — ποιες γλυφές μπαίνουν, και γιατί — που καλύπτεται στις σημειώσεις για το κλείσιμο υποσυνόλου γραμματοσειράς και τις γλυφές shaping. Αν η παρτίδα σας είναι αργή για λόγους που καταλήγουν να μην είναι καθόλου γραμματοσειρές, η ανάλυση της εξόδου αναφορών με γραμματοσειρές και εικόνες δείχνει πού συνήθως πηγαίνει ο άλλος χρόνος, και η μελέτη περίπτωσης του σφάλματος σειράς υποσυνόλου γραμματοσειράς στο EndDoc είναι μια υπενθύμιση ότι η ορθότητα subsetting και η ταχύτητα subsetting είναι ξεχωριστά προβλήματα

Το HotPDF είναι ένα native VCL PDF συστατικό για Delphi και C++Builder, και η κρυφή μνήμη υποσυνόλων είναι μέρος της βιβλιοθήκης παρά μια υπηρεσία προσθήκης, οπότε ένας διακομιστής αναφορών την αποκτά ορίζοντας μια διαδρομή φακέλου — δείτε τη σελίδα του HotPDF συστατικού για την πλήρη λίστα χαρακτηριστικών γραμματοσειρών και απόδοσης