HotPDF laster en PDF fra enhver kilde med tilfeldig tilgang du implementerer, og THPDFCoalescingRandomAccessSource pakker inn den kilden slik at parserens spredte små lesinger blir til et begrenset sett med bufrede blokkområder med asynkron forhåndshenting. På et dokument som serveres over HTTP-områdeforespørsler, er dette forskjellen mellom noen få hundre rundturer og noen få dusin
Ingenting ved parseren endres. Du kaller fortsatt LoadFromRandomAccessSource, samme dokumentobjekt kommer tilbake, og samme side-API fungerer. Det som endres, er trafikken under overflaten
Hvorfor laster samme PDF øyeblikkelig lokalt, men snegler seg av gårde over nettverket?
Fordi en PDF-parser ikke leser en fil, den navigerer en. Den søker til slutten etter startxref, hopper tilbake til kryssreferansetabellen, løser opp avslutningsordboken, følger en referanse til katalogen, deretter til rotnoden i sidetreet, deretter til en sidenode, deretter til dens ressursordbok. Hvert av disse trinnene leser titalls byte fra en annen offset
På en lokal fil er dette mønsteret nesten gratis: operativsystemet har allerede den omkringliggende 4 KiB-siden bufret, så den andre lesingen koster en memcpy. Over en nettverkstransport finnes ingen slik lokalitet. Hver lesing er en forespørsel med sin egen ventetid, og 300 sekvensielle forespørsler à 40 ms hver blir tolv sekunder brukt nesten utelukkende på venting. Fiksen er ikke å lese mindre; parseren trenger nøyaktig det den ber om. Fiksen er å få hver fysiske lesing til å dekke mer av det den neste logiske lesingen vil ønske
Hva sammenslåingen endrer
Den sammenslående kilden runder hver lesing opp til en blokk og bufrer blokken. BlockSize er 262 144 byte som standard og MaxCacheBytes er 2 097 152, slik at åtte blokker er tilstede som standard og fjernes i minst-nylig-brukt-rekkefølge mot et hardt bytebudsjett. Parserens 40-byte-lesing av en avslutningsnøkkel trekker med seg de 256 KiB rundt den, og de neste titalls lesingene i det nabolaget, der kryssreferanse- og katalogdata bor, betjenes fra minnet
Din egen kilde forblir enkel. Implementer GetSize og ReadAt, overstyr ReadAtCancellable hvis transporten din kan avbryte underveis, og la innpakningen håndtere caching, sammenslåing og forhåndshenting
type
THttpRangeSource = class(THPDFRandomAccessSource)
private
FClient: TMyHttpClient;
FUrl: string;
FSize: Int64;
public
function GetSize: Int64; override;
function ReadAt(Offset: Int64; var Buffer; Count: Longint): Longint; override;
function ReadAtCancellable(Offset: Int64; var Buffer; Count: Longint;
CancellationToken: THPDFCancellationToken): Longint; override;
end;
var
Raw: THttpRangeSource;
Cached: THPDFCoalescingRandomAccessSource;
Pdf: THotPDF;
begin
Raw := THttpRangeSource.Create('https://files.example.com/contract.pdf');
// OwnsSource=True: innpakningen frigjør Raw sammen med seg selv
Cached := THPDFCoalescingRandomAccessSource.Create(Raw, True, 262144, 8388608);
Pdf := THotPDF.Create(nil);
try
Cached.AsyncPrefetchEnabled := True;
Cached.AdaptiveReadAheadEnabled := True;
Cached.MaxReadAheadBlocks := 8;
if Pdf.LoadFromRandomAccessSource(Cached, True) = 1 then
RenderFirstPage(Pdf);
finally
Pdf.Free;
end;
end;
Hvor langt frem bør den lese?
Adaptiv forhåndslesing besvarer det spørsmålet per dokument i stedet for å tvinge deg til å gjette. Med AdaptiveReadAheadEnabled satt, vokser vinduet gjennom 1, 2, 4 og 8 blokker etter hvert som vedvarende fremadrettede lesinger akkumuleres, og det overskrider aldri MaxReadAheadBlocks eller den konfigurerte cachekapasiteten. I det øyeblikket en lesing ankommer som ikke ligger omtrent der den forrige sluttet, kollapser vinduet og forhåndshenting undertrykkes
SequentialReadToleranceBytes, som er 4096 som standard, definerer «omtrent». Lesinger som havner innenfor den avstanden fra slutten av den forrige lesingen, telles fortsatt som sekvensielle, noe som betyr noe fordi en PDF-parser som går gjennom en innholdsstrøm, ikke produserer perfekt sammenhengende offset-er; den hopper over et lengdefelt her, en innebygd ordbok der. Settes toleransen for lavt, klassifiseres et normalt fremadrettet søk som tilfeldig, så forhåndslesing kobles aldri inn. Settes den for høyt, ser reell tilfeldig tilgang sekvensiell ut, så du henter megabyte ingen vil ha. Standarden er kalibrert for gjennomgang av innholdsstrømmer, og statistikken vil fortelle deg om transporten din er uenig
Denne asymmetrien er bevisst: vekst er gradvis, kollaps er umiddelbar. Overhenting på en arbeidsbelastning med tilfeldig tilgang koster reell båndbredde og reelle penger på målte transporter, så den billige feilen foretrekkes fremfor den dyre
Avbrytelse som faktisk stopper overføringen
Basisklassen erklærer ReadAtCancellable, og den sammenslående kilden respekterer den fra ende til annen. Når en forgrunnslesing ankommer for et område en pågående forhåndshenting ikke betjener, avbrytes forhåndshentingen i stedet for å få fullføre, slik at brukerens sideforespørsel ikke settes i kø bak spekulativ trafikk. Standardimplementasjonen på THPDFRandomAccessSource faller tilbake til en vanlig ReadAt, noe som betyr at funksjonen er valgfri per transport: HTTP-klienter som støtter forespørselsavbrudd, får ekte avbrytelse, og enklere kilder fortsetter å fungere uendret
Kombiner det med et avbrytelsestoken tredd gjennom brukergrensesnittet ditt, og en bruker som lukker et dokument, stopper faktisk nettverkstrafikken i stedet for å vente på at den tømmes. Den samme tokenmodellen ligger under køleggingen beskrevet i bakgrunnsrendering med en forespørselskø, så ett token kan dekke hele veien fra visningsområdet til socket-en
Å lese statistikken for områdecachen
GetStatistics fyller en THPDFRangeCacheStatistics-record som skiller hva transporten din gjorde fra hva cachen gjorde. SourceReadCount og SourceBytesRead er fysisk trafikk. CacheHitCount og CacheMissCount er logisk trafikk. SequentialReadCount og RandomReadCount viser hvordan tilgangsmønsteret ble klassifisert, CurrentReadAheadBlocks og PeakReadAheadBlocks viser hvor langt vinduet åpnet seg, og PrefetchRequestCount, PrefetchCompletedCount, PrefetchCancelledCount og SuppressedPrefetchCount viser om spekulasjonen lønte seg
var
S: THPDFRangeCacheStatistics;
begin
Cached.GetStatistics(S);
Log(Format('physical %d reads / %d bytes, hits %d, misses %d',
[S.SourceReadCount, S.SourceBytesRead, S.CacheHitCount, S.CacheMissCount]));
Log(Format('pattern: %d sequential, %d random, peak window %d blocks',
[S.SequentialReadCount, S.RandomReadCount, S.PeakReadAheadBlocks]));
Log(Format('prefetch: %d issued, %d completed, %d cancelled, %d suppressed',
[S.PrefetchRequestCount, S.PrefetchCompletedCount,
S.PrefetchCancelledCount, S.SuppressedPrefetchCount]));
end;
Tre avlesninger forteller deg hva som bør endres. Mange avbrutte forhåndshentinger med et høyt antall tilfeldige lesinger betyr at dokumentet aksesseres ute av rekkefølge, så senk MaxReadAheadBlocks og slutt å betale for båndbredde du forkaster. Mange bom med et toppvindu fortsatt på 1 betyr at toleransen avviser et mønster som i praksis er sekvensielt, så hev SequentialReadToleranceBytes. Og byte lest langt utover filstørrelsen betyr at cachen tresker, så hev MaxCacheBytes før du rører noe annet
Lineariserte filer endrer regnestykket
Hvis du styrer produsenten, endrer linearisering av dokumentet problemet i stedet for å optimalisere det. En linearisert PDF plasserer objektene til den første siden og en hint-tabell fremst i filen, slik at en viser kan rendre side én fra den innledende megabyten uten å se resten. HotPDF eksponerer den veien direkte gjennom GetProgressiveLinearizedLoadInfo og ReadProgressiveLinearizedFirstPageSection, og skrivesiden dekkes i å generere lineariserte PDF-er med hint-tabeller
Begge teknikkene lar seg kombinere. Sammenslåing gjør ethvert dokument tålelig over en treg forbindelse; linearisering får den første siden til å ankomme raskt for dokumenter du selv produserer. For filer som ligger på en lokal disk, men er for store til å holde i minnet, er de mappede fil- og lat-strøm-veiene beskrevet i den direkte fil-API-arbeidsflyten vanligvis det bedre verktøyet, siden det ikke finnes noen rundtur-ventetid å amortisere i utgangspunktet
HotPDF er en native VCL PDF-komponent for Delphi og C++Builder, uten noen ekstern DLL for parseren og med full kildekode tilgjengelig. API-et for tilfeldig-tilgang-kilder, den sammenslående innpakningen og inngangspunktene for progressiv lasting er dokumentert på HotPDF sin side for Delphi PDF-komponent