O HotPDF consegue manter subconjuntos de tipos de letra TrueType e OpenType em disco e reutilizá-los entre documentos e entre execuções do processo, pelo que um lote que renderiza dez mil extratos com os mesmos três tipos de letra faz o subsetting desses tipos uma única vez em vez de dez mil vezes. A cache configura-se com duas propriedades, inspeciona-se com um registo, e é segura deixar ligada: uma falha de cache recua para o subsetting normal em memória e nunca impede um documento de ser produzido
O subsetting é caro por uma razão. Construir um subconjunto significa percorrer o fecho de glifos, reescrever loca e glyf, reconstruir cmap e hmtx, e emitir um mapeamento CID que o PDF consiga endereçar. Para um documento esse custo desaparece no ruído. Para um servidor de relatórios que produz documentos num ciclo, é frequentemente o maior bloco individual de tempo de CPU na execução
O que torna uma cache hit possível
Quatro coisas têm de corresponder: o conteúdo do tipo de letra, o conjunto de glifos usados, o modo de subsetting, e o esquema da cache. Falhe um só e o HotPDF faz o subsetting de raiz, porque um subconjunto só é reutilizável quando teria sido byte-idêntico de qualquer forma
O conjunto de glifos é a condição que surpreende as pessoas. Duas faturas que diferem num único nome de cliente usam conjuntos de glifos diferentes, e por isso produzem subconjuntos diferentes e entradas de cache diferentes. A cache compensa quando os documentos partilham um repertório de glifos — extratos de um modelo fixo, formulários cujos dados variáveis são numéricos, catálogos retirados de uma base de dados de produtos — e não compensa nada quando cada documento desenha uma fatia diferente de um tipo CJK grande. Meça antes de assumir em que caso está
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;
Como sabe se a cache está a fazer alguma coisa?
GetFontSubsetCacheInfo devolve nove contadores, e o rácio entre os dois primeiros responde à pergunta diretamente. HitCount e MissCount dão a taxa de acertos. WriteCount e EvictionCount mostram se as entradas sobrevivem tempo suficiente para serem reutilizadas ou estão a ser empurradas para fora por um orçamento demasiado pequeno. CurrentBytes e FileCount reportam o que está em disco agora
Os três restantes são aqueles sobre os quais vale a pena alertar. CorruptCount conta entradas que falharam a validação e foram removidas — algumas depois de um encerramento incómodo são normais, um fluxo constante significa que o armazenamento é pouco fiável. RejectedCount conta entradas recusadas antes do uso. WriteFailureCount conta entradas que não puderam ser escritas de todo, o que normalmente significa um problema de permissões na pasta em vez de algo sobre tipos de letra. Nenhum destes três para a geração de documentos, que é exatamente porque tem de olhar para eles: uma cache que silenciosamente nunca escreve parece igual por fora a uma cache que funciona, exceto na fatura de CPU
Remoção, orçamentos e o momento em que se encolhe um
FontSubsetCacheMaxBytes tem a predefinição de 268435456 bytes, ou seja 256 MiB, e pode ser baixado em runtime. Baixá-lo despoleta remoção do menos recentemente usado imediata em vez de esperar pela próxima escrita, pelo que um serviço que reaja a pressão de disco consegue libertar espaço no momento em que decide, não num ponto posterior que não controla
Definir FontSubsetCacheFolder como uma cadeia vazia desativa o nível de disco sem limpar nada já armazenado, e sem mudar um único byte da saída de tipos de letra. É a propriedade a procurar quando quer isolar a cache durante a resolução de problemas: desligue-a, execute o mesmo lote, e compare os PDFs produzidos. Devem ser idênticos, porque a cache armazena um resultado, não uma política
O que a cache faz quando uma entrada está danificada
Remove-a e faz o subsetting normalmente. Entradas malformadas ou truncadas são rejeitadas antes que o subconjunto consiga chegar a um stream PDF, que é a parte do desenho que mais importa: uma entrada de cache corrompida que chegasse a um documento produziria um PDF com um programa de tipo de letra partido, e essa falha apareceria longe da sua causa — num visualizador, na máquina de um cliente, semanas depois
As escritas são atómicas, pelo que um leitor nunca observa uma entrada meio escrita, e um bloqueio a meio da escrita deixa a cache consistente em vez de envenenada. As entradas de subconjunto compacto retêm os dados de remapeamento CID que os dicionários de tipo de letra do PDF/A exigem, pelo que um subconjunto em cache continua a ser um subconjunto conforme — a saída de arquivo não tem de contornar a cache para se manter válida
// 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');
Onde colocar a pasta numa implementação real
Três propriedades decidem isto: a pasta tem de ser gravável pela conta com que o serviço corre, deve sentar-se em armazenamento local em vez de numa partilha de rede, e não deve estar dentro de um diretório que um passo de implantação apague. Uma cache numa partilha transforma cada falha numa ida e volta e cada acerto em duas; uma cache sob uma pasta de aplicação que o instalador recria é uma cache que arranca a frio depois de cada atualização
Para serviços com múltiplas instâncias, dê a cada instância a sua própria pasta a menos que tenha confirmado que o armazenamento lida com substituição atómica concorrente da forma que espera. O custo de uma entrada duplicada é uma passagem de subsetting extra; o custo de depurar uma corrida de cache partilhada é uma tarde
Quando procurar outra coisa
A cache reduz trabalho repetido. Não reduz o trabalho do primeiro documento, e não ajuda uma carga de trabalho cujos conjuntos de glifos nunca se repetem. Se a sua saída é dominada por um tipo CJK enorme usado em texto imprevisível, a alavanca mais eficaz é o próprio fecho de subsetting — que glifos são puxados, e porquê — coberta nas notas sobre fecho de subconjunto de tipos de letra e modelação de glifos. Se o seu lote é lento por razões que afinal não são tipos de letra de todo, o guia de saída de relatórios com tipos de letra e imagens mostra para onde vai normalmente o outro tempo, e o caso de estudo sobre o bug de ordenação de subconjunto de tipos de letra no EndDoc é um lembrete de que correção de subsetting e velocidade de subsetting são problemas separados
O HotPDF é um componente PDF VCL nativo para Delphi e C++Builder, e a cache de subconjuntos faz parte da biblioteca em vez de ser um serviço extra, pelo que um servidor de relatórios obtém-na definindo um único caminho de pasta — veja a página do componente HotPDF para a lista completa de funcionalidades de tipos de letra e desempenho