Article technique

Cache Disque des Sous-ensembles de Polices HotPDF en Delphi

HotPDF peut conserver les sous-ensembles de polices TrueType et OpenType sur disque et les réutiliser à travers les documents et à travers les exécutions de processus, donc un lot qui rend dix mille relevés avec les trois mêmes polices ne sous-ensemble ces polices qu'une fois au lieu de dix mille fois. Le cache se configure avec deux propriétés, s'inspecte avec un enregistrement, et peut être laissé activé en sécurité : une défaillance du cache retombe sur le sous-ensemble normal en mémoire et n'empêche jamais un document d'être produit

Le sous-ensemble est coûteux pour une raison. Construire un sous-ensemble signifie parcourir la fermeture des glyphes, réécrire loca et glyf, reconstruire cmap et hmtx, et émettre un mappage CID que le PDF peut adresser. Pour un document ce coût disparaît dans le bruit. Pour un serveur de rapports qui produit des documents en boucle, c'est souvent le plus gros bloc unique de temps CPU dans l'exécution

Ce qui rend un succès de cache possible

Quatre choses doivent correspondre : le contenu de police, l'ensemble des glyphes utilisés, le mode de sous-ensemble, et le schéma de cache. Manquez l'un d'entre eux et HotPDF sous-ensemble à partir de zéro, car un sous-ensemble n'est réutilisable que s'il aurait de toute façon été identique octet à octet

L ensemble de glyphes est la condition qui surprend les gens. Deux factures qui diffèrent d'un seul nom de client utilisent des ensembles de glyphes différents, et produisent donc des sous-ensembles différents et des entrées de cache différentes. Le cache paie quand les documents partagent un répertoire de glyphes — relevés depuis un modèle fixe, formulaires dont les données variables sont numériques, catalogues tirés d'une base de données produit — et ne paie rien quand chaque document dessine une tranche différente d'une grosse police CJK. Mesurez avant de présumer dans quel cas vous êtes

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;

Comment savez-vous que le cache fait quoi que ce soit ?

GetFontSubsetCacheInfo renvoie neuf compteurs, et le rapport entre les deux premiers répond à la question directement. HitCount et MissCount donnent le taux de succès. WriteCount et EvictionCount montrent si les entrées survivent assez pour être réutilisées ou si elles sont poussées dehors par un budget trop petit. CurrentBytes et FileCount signalent ce qui est sur disque en ce moment

Les trois restants sont ceux sur lesquels alerter. CorruptCount compte les entrées qui ont échoué à la validation et ont été retirées — quelques-unes après un arrêt impropre sont normales, un flux constant signifie que le stockage n'est pas fiable. RejectedCount compte les entrées refusées avant usage. WriteFailureCount compte les entrées qui n'ont pas pu être écrites du tout, ce qui signifie habituellement un problème de permissions sur le dossier plutôt que quoi que ce soit concernant les polices. Aucun de ces trois n'arrête la génération de documents, ce qui est exactement pourquoi vous devez les regarder : un cache qui n'écrit jamais silencieusement paraît identique de l'extérieur à un cache qui fonctionne, à l'exception de la facture CPU

Éviction, budgets et le moment où vous en réduisez un

FontSubsetCacheMaxBytes vaut par défaut 268435456 octets, soit 256 MiB, et peut être abaissé à l'exécution. L abaisser déclenche une éviction immédiate des entrées les moins récemment utilisées plutôt que d'attendre la prochaine écriture, donc un service qui réagit à la pression disque peut libérer de l'espace au moment où il le décide, pas à un moment ultérieur qu'il ne contrôle pas

Régler FontSubsetCacheFolder à une chaîne vide désactive le niveau disque sans rien effacer de ce qui est déjà stocké, et sans changer un seul octet de sortie de police. C'est la propriété à atteindre quand vous voulez isoler le cache pendant le dépannage : désactivez-le, exécutez le même lot, et comparez les PDF produits. Ils devraient être identiques, car le cache stocke un résultat, pas une politique

Ce que le cache fait quand une entrée est endommagée

Il la supprime et sous-ensemble normalement. Les entrées malformées ou tronquées sont rejetées avant que le sous-ensemble puisse atteindre un flux PDF, ce qui est la partie du design qui compte le plus : une entrée de cache corrompue qui atteindrait un document produirait un PDF avec un programme de police cassé, et cette défaillance affleurerait loin de sa cause — dans une visionneuse, sur une machine client, des semaines plus tard

Les écritures sont atomiques, donc un lecteur n'observe jamais une entrée à demi écrite, et un crash en milieu d'écriture laisse le cache cohérent plutôt qu'empoisonné. Les entrées de sous-ensemble compactes conservent les données de remappage CID que les dictionnaires de polices PDF/A exigent, donc un sous-ensemble mis en cache reste un sous-ensemble conforme — la sortie d'archivage n'a pas à contourner le cache pour rester valide

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

Où placer le dossier dans un déploiement réel

Trois propriétés décident cela : le dossier doit être inscriptible par le compte sous lequel le service s'exécute, il devrait se trouver sur un stockage local plutôt qu'un partage réseau, et il ne devrait pas être à l'intérieur d'un répertoire qu'une étape de déploiement efface. Un cache sur un partage transforme chaque échec en un aller-retour et chaque succès en deux ; un cache sous un dossier applicatif que l'installateur recrée est un cache qui démarre à froid après chaque mise à jour

Pour les services multi-instances, donnez à chaque instance son propre dossier à moins que vous n'ayez confirmé que le stockage gère le remplacement atomique concurrent comme vous l'attendez. Le coût d'une entrée dupliquée est une passe de sous-ensemble supplémentaire ; le coût de déboguer une compétition de cache partagé est un après-midi

Quand s'orienter vers autre chose

Le cache réduit le travail répété. Il ne réduit pas le travail du premier document, et n'aide pas une charge dont les ensembles de glyphes ne se répètent jamais. Si votre sortie est dominée par une énorme police CJK utilisée à travers du texte imprévisible, le levier le plus efficace est la fermeture de sous-ensemble elle-même — quels glyphes sont tirés, et pourquoi — couvert dans les notes sur la fermeture de sous-ensemble de police et le façonnage des glyphes. Si votre lot est lent pour des raisons qui se révèlent n'être pas les polices du tout, la visite guidée de sortie de rapport avec polices et images montre où va habituellement l'autre temps, et l'étude de cas du bogue d'ordre de sous-ensemble de police EndDoc est un rappel que l'exactitude du sous-ensemble et la vitesse du sous-ensemble sont des problèmes séparés

HotPDF est un composant PDF VCL natif pour Delphi et C++Builder, et le cache de sous-ensemble fait partie de la bibliothèque plutôt que d'un service additionnel, donc un serveur de rapports l'obtient en réglant un seul chemin de dossier — voir la page composant HotPDF pour la liste complète des fonctionnalités polices et performances