Articolo tecnico

Memorizzare i sottoinsiemi dei font su disco con HotPDF in Delphi

HotPDF può conservare i sottoinsiemi di font TrueType e OpenType su disco e riusarli tra documenti ed esecuzioni del processo, così un batch che renderizza diecimila estratti conto con gli stessi tre font fa il subsetting di quei font una sola volta invece di diecimila volte. La cache si configura con due proprietà, si ispeziona con un record, ed è sicura lasciarla attiva: un fallimento della cache ricade sul subsetting in memoria normale e non ferma mai la produzione di un documento

Il subsetting è costoso per un motivo. Costruire un sottoinsieme significa percorrere la chiusura dei glifi, riscrivere loca e glyf, ricostruire cmap e hmtx, ed emettere una mappatura CID che il PDF possa indirizzare. Per un singolo documento quel costo svanisce nel rumore. Per un server di report che produce documenti in un ciclo, è spesso il singolo blocco più grande di tempo CPU nell'intera esecuzione

Cosa rende possibile un hit nella cache

Quattro cose devono coincidere: il contenuto del font, l'insieme dei glifi usati, la modalità di subsetting e lo schema della cache. Mancane una qualsiasi e HotPDF fa il subsetting da zero, perché un sottoinsieme è riusabile solo quando sarebbe stato byte-identico comunque

L'insieme dei glifi è la condizione che sorprende le persone. Due fatture che differiscono per un solo nome cliente usano insiemi di glifi diversi, e quindi producono sottoinsiemi diversi e voci di cache diverse. La cache rende quando i documenti condividono un repertorio di glifi — estratti conto da un template fisso, moduli i cui dati variabili sono numerici, cataloghi tratti da un unico database di prodotto — e non rende nulla quando ogni documento disegna una fetta diversa di un carattere CJK grande. Misura prima di presumere in quale caso sei

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;

Come sai che la cache sta facendo qualcosa?

GetFontSubsetCacheInfo restituisce nove contatori, e il rapporto tra i primi due risponde alla domanda direttamente. HitCount e MissCount danno l'hit rate. WriteCount ed EvictionCount mostrano se le voci sopravvivono abbastanza a lungo da essere riusate o se vengono spinte fuori da un budget troppo piccolo. CurrentBytes e FileCount riportano cosa c'è ora su disco

Gli altri tre sono quelli per cui vale la pena attivare alert. CorruptCount conta le voci che hanno fallito la validazione e sono state rimosse — alcune dopo uno spegnimento non pulito sono normali, un flusso costante significa che lo storage è inaffidabile. RejectedCount conta le voci rifiutate prima dell'uso. WriteFailureCount conta le voci che non hanno potuto essere scritte affatto, il che di solito indica un problema di permessi sulla cartella anziché qualcosa che riguarda i font. Nessuno di questi tre ferma la generazione dei documenti, il che è esattamente il motivo per cui devi guardarli: una cache che silenziosamente non scrive mai sembra da fuori uguale a una cache che funziona, tranne per la bolletta CPU

Evacuazione, budget e il momento in cui ne riduci uno

FontSubsetCacheMaxBytes vale predefinito 268435456 byte, cioè 256 MiB, e può essere abbassato a runtime. Abbassarlo innesca un'evacuazione least-recently-used immediata anziché attendere la prossima scrittura, così un servizio che reagisce alla pressione sul disco può liberare spazio nel momento in cui decide di farlo, non in un qualche momento successivo che non controlla

Impostare FontSubsetCacheFolder a una stringa vuota disabilita il tier su disco senza cancellare nulla di già memorizzato, e senza cambiare un solo byte dell'output dei font. È la proprietà da raggiungere quando vuoi isolare la cache durante il troubleshooting: spegnila, esegui lo stesso batch e confronta i PDF prodotti. Dovrebbero essere identici, perché la cache memorizza un risultato, non una policy

Cosa fa la cache quando una voce è danneggiata

La rimuove e fa il subsetting normalmente. Voci malformate o troncate vengono rifiutate prima che il sottoinsieme possa raggiungere uno stream PDF, che è la parte del progetto che conta di più: una voce di cache corrotta che fosse finita in un documento produrrebbe un PDF con un programma di font rotto, e quel fallimento emergerebbe lontano dalla propria causa — in un visualizzatore, su una macchina del cliente, settimane dopo

Le scritture sono atomiche, così un lettore non osserva mai una voce scritta a metà, e un crash a metà scrittura lascia la cache consistente anziché avvelenata. Le voci di sottoinsieme Compat conservano i dati di rimappatura CID che i dizionari di font PDF/A richiedono, così un sottoinsieme cached è ancora un sottoinsieme conforme — l'output archivistico non deve bypassare la cache per restare valido

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

Dove mettere la cartella in un deployment reale

Tre proprietà lo decidono: la cartella deve essere scrivibile dall'account con cui gira il servizio, dovrebbe stare su storage locale anziché su una condivisione di rete, e non dovrebbe stare dentro una directory che uno step di deployment cancella. Una cache su una condivisione trasforma ogni miss in un round trip e ogni hit in due; una cache sotto una cartella applicazione che l'installer ricrea è una cache che parte a freddo dopo ogni aggiornamento

Per servizi multi-istanza, dai a ciascuna istanza la propria cartella a meno che tu non abbia confermato che lo storage gestisce la sostituzione atomica concorrente come ti aspetti. Il costo di una voce duplicata è un passaggio di subsetting in più; il costo di debug di una race sulla cache condivisa è un pomeriggio

Quando rivolgersi a qualcos'altro

La cache riduce il lavoro ripetuto. Non riduce il lavoro del primo documento, e non aiuta un carico di lavoro i cui insiemi di glifi non si ripetono mai. Se il tuo output è dominato da un enorme carattere CJK usato su testo imprevedibile, la leva più efficace è la chiusura di subsetting stessa — quali glifi vengono tirati dentro, e perché — coperta nelle note sulla chiusura di subsetting dei font e shaping dei glifi. Se il tuo batch è lento per ragioni che alla fine non sono affatto i font, la guida sull'output dei report con font e immagini mostra dove va di solito il resto del tempo, e il caso di studio sul bug di ordinamento del subsetting dei font a EndDoc è un promemoria che correttezza del subsetting e velocità del subsetting sono problemi separati

HotPDF è un componente PDF VCL nativo per Delphi e C++Builder, e la cache dei sottoinsiemi fa parte della libreria anziché essere un servizio aggiuntivo, così un server di report la ottiene impostando un solo percorso di cartella — vedi la pagina del componente HotPDF per la lista completa delle funzionalità di font e prestazioni