Teknisk artikel

Caching af skrifttype-delmængder på disk med HotPDF i Delphi

HotPDF kan beholde TrueType- og OpenType-skrifttype-delmængder på disk og genbruge dem på tværs af dokumenter og på tværs af proces-kørsler, så en batch der renderer ti tusind opgørelser med de samme tre skrifttyper, delmængder disse skrifttyper én gang i stedet for ti tusinde gange. Cachen konfigureres med to egenskaber, inspiceres med én record, og er sikker at lade stå tændt: et cache-fejl falder tilbage til normal in-memory-subsetting og stopper aldrig et dokument i at blive produceret

Subsetting er dyrt af en grund. At bygge en delmængde betyder at gå glyph-closure'en efter, omskrive loca og glyf, genopbygge cmap og hmtx, og emitte en CID-mapping, PDF'en kan adressere. For ét dokument forsvinder den omkostning i støjen. For en rapport-server, der producerer dokumenter i en løkke, er det ofte den største enkelte blok af CPU-tid i kørslen

Hvad gør et cache-hit muligt?

Fire ting skal matche: skrifttype-indholdet, sættet af brugte glyffer, subset-tilstanden og cache-skemaet. Misss ét, og HotPDF delmængder fra bunden, fordi en delmængde kun er genbrugelig, når den alligevel ville have været byte-identisk

Glyph-sættet er den betingelse, der overrasker folk. To fakturaer, der adskiller sig ved et enkelt kundenavn, bruger forskellige glyph-sæt og producerer derfor forskellige delmængder og forskellige cache-poster. Cachen betaler sig, når dokumenter deler et glyph- repertoire — opgørelser fra en fast skabelon, formularer hvis variable data er numeriske, kataloger trukket fra én produkt-database — og betaler intet, når hvert dokument tegner et forskelligt udsnit af en stor CJK-skrifttype. Mål, før du antager, hvilket tilfælde du er i

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;

Hvordan ved du, at cachen gør noget som helst?

GetFontSubsetCacheInfo returnerer ni tællere, og forholdet mellem de to første besvarer spørgsmålet direkte. HitCount og MissCount giver hit-raten. WriteCount og EvictionCount viser, om poster overlever længe nok til at blive genbrugt, eller om de skubbes ud af et budget, der er for lille. CurrentBytes og FileCount rapporterer, hvad der er på disk lige nu

De resterende tre er dem, det kan betale sig at alarmere på. CorruptCount tæller poster, der fejlede validering og blev fjernet — et par stykker efter en uregelmæssig nedlukning er normale, en jævn strøm betyder, at lagringen er upålidelig. RejectedCount tæller poster, der blev afvist før brug. WriteFailureCount tæller poster, der slet ikke kunne skrives, hvilket normalt betyder et tilladelses-problem på mappen frem for noget om skrifttyper. Ingen af disse tre stopper dokument-generering, hvilket er præcis grunden til, at du er nødt til at kigge på dem: en cache, der tavst aldrig skriver, ser den samme ud udefra som en cache, der virker, bortset fra CPU-regningen

Eviction, budgetter og det øjeblik du formindsker et

FontSubsetCacheMaxBytes er som standard 268435456 byte, det er 256 MiB, og den kan sænkes ved runtime. At sænke den udløser øjeblikkelig least-recently-used-eviction frem for at vente på næste skrivning, så en tjeneste, der reagerer på disk-tryk, kan frigøre plads i det øjeblik, den beslutter det, frem for på et senere tidspunkt, den ikke kontrollerer

At sætte FontSubsetCacheFolder til en tom streng deaktiverer disk-niveauet uden at rydde noget, der allerede er lagret, og uden at ændre en eneste byte af skrifttype-output. Det er den egenskab, du skal gribe ud efter, når du vil isolere cachen under fejlfinding: slå den fra, kør samme batch, og sammenlign de producerede PDF'er. De bør være identiske, fordi cachen lagrer et resultat, ikke en politik

Hvad cachen gør, når en post er beskadiget

Den fjerner den og delmængder normalt. Malformede eller trunkerede poster afvises, før delmængden kan nå en PDF-strøm, hvilket er den del af designet, der betyder mest: en korrumperet cache-post, der kom ind i et dokument, ville producere en PDF med et brudt skrifttype-program, og den fejl ville dukke op langt fra sin årsag — i en fremviser, på en kundes maskine, uger senere

Skrivninger er atomare, så en læser observerer aldrig en halvskrevet post, og et crash midt i skrivning efterlader cachen konsistent frem for forgiftet. Komprimerede delmængde-poster bevarer CID-remapping-data, som PDF/A-skrifttype-dictionaries kræver, så en cache-lagret delmængde stadig er en overholdende delmængde — arkiv-output behøver ikke at omgå cachen for at forblive gyldigt

// 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');

Hvor skal mappen placeres i en rigtig udrulning?

Tre egenskaber afgør dette: mappen skal være skrivbar for den konto, tjenesten kører som, den bør ligge på lokal lagring frem for en netværks-share, og den bør ikke ligge inde i et bibliotek, som et udrulnings-trin rydder. En cache på en share forvandler hvert miss til en rundtur og hvert hit til to; en cache under et program-bibliotek, som installatoren genskaber, er en cache, der starter kold efter hver opdatering

For multi-instance-tjenester, giv hver instans sin egen mappe, medmindre du har bekræftet, at lagringen håndterer samtidig atomisk erstatning, som du forventer. Omkostningen ved en dubleret post er ét ekstra subsetting-pass; omkostningen ved at debugge et shared-cache-race er en eftermiddag

Når du skal gribe ud efter noget andet

Cachen reducerer gentaget arbejde. Den reducerer ikke arbejdet i det første dokument, og den hjælper ikke en arbejdsbyrning, hvis glyph-sæt aldrig gentager sig. Hvis dit output domineres af én enorm CJK-skrifttype brugt på tværs af uforudsigelig tekst, er den mere virkningsfulde arm selve subsetting-closure'en — hvilke glyffer der trækkes ind, og hvorfor — dækket i noterne om skrifttype-subset-closure og shaping-glyffer. Hvis din batch er lang af årsager, der viser sig slet ikke at være skrifttyper, viser gennemgangen af rapport-output med skrifttyper og billeder, hvor den anden tid normalt går, og case-studiet om EndDoc-skrifttype-subset-rækkefølge-bug er en påmindelse om, at subsetting-korrekthed og subsetting-hastighed er separate problemer

HotPDF er en native VCL PDF-komponent til Delphi og C++Builder, og subset-cachen er del af biblioteket frem for en add-on-tjeneste, så en rapport-server får den ved at sætte én mappe-sti — se HotPDF-komponent-siden for den fulde skrifttype- og ydeevne-funktionsliste