HotPDF lataa PDF-tiedoston mistä tahansa toteuttamastasi satunnaiskäyttölähteestä, ja THPDFCoalescingRandomAccessSource kääri tämän lähteen niin, että jäsentimen hajanaiset pienet luvut muuttuvat rajatuksi joukoksi välimuistiin tallennettuja lohkoalueita asynkronisella ennakkohaulla. Asiakirjassa, jota tarjoillaan HTTP-aluepyyntöjen kautta, tämä on ero muutaman sadan edestakaisen kutsun ja muutaman tusinan välillä
Jäsentimessä ei muutu mikään. Kutsut edelleen LoadFromRandomAccessSource-metodia, sama asiakirjaolio palautuu, ja sama sivu-API toimii. Se, mikä muuttuu, on liikenne sen alla
Miksi sama PDF latautuu paikallisesti hetkessä mutta ryömii verkon yli?
Koska PDF-jäsennin ei lue tiedostoa, se navigoi siinä. Se hakeutuu loppuun startxref-arvoa varten, hyppää takaisin ristiviitetaulukkoon, ratkaisee trailer-sanakirjan, seuraa viittausta Catalog-objektiin, sitten sivupuun juureen, sitten sivusolmuun, sitten sen resurssisanakirjaan. Jokainen näistä vaiheista lukee kymmeniä tavuja eri offsetista
Paikallisessa tiedostossa tämä kuvio on lähes ilmainen: käyttöjärjestelmällä on jo ympäröivä 4 KiB:n sivu välimuistissa, joten toinen luku maksaa yhden memcpy-kutsun. Verkkosiirrossa vastaavaa paikallisuutta ei ole. Jokainen luku on oma pyyntönsä omalla viiveellään, ja 300 peräkkäistä pyyntöä 40 ms:n viiveellä kukin on kaksitoista sekuntia, joka kuluu lähes kokonaan odotukseen. Ratkaisu ei ole lukea vähemmän; jäsennin tarvitsee juuri sen, mitä se pyytää. Ratkaisu on saada jokainen fyysinen luku kattamaan enemmän siitä, mitä seuraava looginen luku tulee tarvitsemaan
Mitä yhdistely muuttaa
Yhdistelevä lähde pyöristää jokaisen luvun ylöspäin lohkoon ja tallentaa lohkon välimuistiin. BlockSize on oletuksena 262 144 tavua ja MaxCacheBytes 2 097 152 tavua, joten kahdeksan lohkoa on oletuksena muistissa, ja ne häädetään pisimpään käyttämättömänä olleen periaatteella kiinteää tavubudjettia vasten. Jäsentimen 40 tavun luku trailer-avaimesta tuo mukanaan sitä ympäröivän 256 KiB:n alueen, ja seuraava tusina lukuja siltä alueelta, jossa ristiviite- ja catalog-data sijaitsee, palvellaan muistista
Oma lähteesi pysyy yksinkertaisena. Toteuta GetSize ja ReadAt, ylikirjoita ReadAtCancellable, jos siirtosi voi keskeyttää kesken toiminnan, ja anna kääreen hoitaa välimuistitus, yhdistely ja ennakkohaku
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: kääre vapauttaa Raw:n itsensä mukana
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;
Kuinka pitkälle eteenpäin sen pitäisi lukea?
Adaptiivinen ennakkoluku vastaa tähän kysymykseen asiakirjakohtaisesti sen sijaan, että se pakottaisi sinut arvaamaan. Kun AdaptiveReadAheadEnabled on asetettu, ikkuna kasvaa arvojen 1, 2, 4 ja 8 lohkoa kautta jatkuvien eteenpäin suuntautuvien lukujen kertyessä, eikä se koskaan ylitä arvoa MaxReadAheadBlocks tai määritettyä välimuistikapasiteettia. Heti kun saapuu luku, joka ei ole suunnilleen siinä, mihin edellinen päättyi, ikkuna romahtaa ja ennakkohaku keskeytetään
SequentialReadToleranceBytes, oletuksena 4 096, määrittää tuon "suunnilleen". Luvut, jotka osuvat tämän etäisyyden sisälle edellisen luvun lopusta, lasketaan silti peräkkäisiksi, millä on merkitystä, koska sisältövirtaa läpikäyvä PDF-jäsennin ei tuota täysin yhtenäisiä offsetteja; se hyppää tässä pituuskentän yli, siellä upotetun sanakirjan yli. Aseta toleranssi liian matalaksi, ja normaali eteenpäin suuntautuva luku luokitellaan satunnaiseksi, jolloin ennakkoluku ei koskaan käynnisty. Aseta se liian korkeaksi, ja aito satunnaiskäyttö näyttää peräkkäiseltä, jolloin haet megatavuja, joita kukaan ei halua. Oletusarvo on kalibroitu sisältövirran läpikäyntiä varten, ja tilastot kertovat, jos siirtosi on eri mieltä
Tämä epäsymmetria on tarkoituksellinen: kasvu on asteittaista, romahdus on välitöntä. Ylihakeminen satunnaiskäyttöisessä kuormassa maksaa todellista kaistanleveyttä ja todellista rahaa mitatuilla siirtoyhteyksillä, joten halpa virhe on kalliin virheen sijaan suositeltu
Peruutus, joka todella pysäyttää siirron
Kantaluokka julistaa ReadAtCancellable-metodin, ja yhdistelevä lähde noudattaa sitä alusta loppuun. Kun etualan luku saapuu alueelle, jota käynnissä oleva ennakkohaku ei palvele, ennakkohaku perutaan sen sijaan, että se jätettäisiin loppuun, joten käyttäjän sivupyyntö ei jää jonoon spekulatiivisen liikenteen taakse. THPDFRandomAccessSource-luokan oletustoteutus palaa tavalliseen ReadAt-kutsuun, mikä tarkoittaa, että ominaisuus on siirtokohtaisesti valinnainen: HTTP-asiakkaat, jotka tukevat pyynnön keskeytystä, saavat aidon peruutuksen, ja yksinkertaisemmat lähteet jatkavat toimintaansa muuttumattomina
Yhdistä tämä käyttöliittymäsi läpi kulkevaan peruutustunnukseen, ja käyttäjän asiakirjan sulkeminen todella pysäyttää verkkoliikenteen sen sijaan, että sen annettaisiin valua loppuun. Sama tunnusmalli on pohjana artikkelissa taustarenderöinti pyyntöjonon avulla kuvatulle jonotukselle, joten yksi tunnus voi kattaa koko polun näyttöalueesta pistokkeeseen
Aluevälimuistin tilastojen lukeminen
GetStatistics täyttää THPDFRangeCacheStatistics-tietueen, joka erottelee siirtosi tekemiset välimuistin tekemisistä. SourceReadCount ja SourceBytesRead ovat fyysistä liikennettä. CacheHitCount ja CacheMissCount ovat loogista liikennettä. SequentialReadCount ja RandomReadCount näyttävät, miten käyttökuvio luokiteltiin, CurrentReadAheadBlocks ja PeakReadAheadBlocks näyttävät, kuinka pitkälle ikkuna avautui, ja PrefetchRequestCount, PrefetchCompletedCount, PrefetchCancelledCount ja SuppressedPrefetchCount näyttävät, kannattiko spekulointi
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;
Kolme lukemaa kertovat, mitä kannattaa muuttaa. Monet peruutetut ennakkohaut korkean satunnaislukumäärän kanssa tarkoittavat, että asiakirjaa käytetään epäjärjestyksessä, joten laske MaxReadAheadBlocks-arvoa ja lopeta maksaminen kaistanleveydestä, jonka hylkäät. Monet osumattomuudet ikkunan huipun jäädessä yhteen tarkoittavat, että toleranssi hylkää kuvion, joka on käytännössä peräkkäinen, joten nosta arvoa SequentialReadToleranceBytes. Ja tiedoston kokoa huomattavasti ylittävä luettujen tavujen määrä tarkoittaa, että välimuisti pyörii ylikierroksilla, joten nosta arvoa MaxCacheBytes ennen kuin koskeet mihinkään muuhun
Linearisoidut tiedostot muuttavat laskutoimituksen
Jos hallitset tuottajaa, asiakirjan linearisointi muuttaa ongelman sen optimoinnin sijaan. Linearisoitu PDF sijoittaa ensimmäisen sivun objektit ja vihjetaulukon tiedoston alkuun, joten katseluohjelma voi renderöidä sivun yksi avaavasta megatavusta näkemättä loppua. HotPDF paljastaa tämän polun suoraan GetProgressiveLinearizedLoadInfo- ja ReadProgressiveLinearizedFirstPageSection-metodien kautta, ja kirjoituspuoli käsitellään artikkelissa linearisoitujen PDF-tiedostojen luonti vihjetaulukoiden kanssa
Molemmat tekniikat yhdistyvät. Yhdistely tekee mistä tahansa asiakirjasta siedettävän hitaan yhteyden yli; linearisointi saa ensimmäisen sivun saapumaan nopeasti asiakirjoissa, jotka tuotat itse. Tiedostoille, jotka asuvat paikallisella levyllä mutta ovat liian suuria mahtuakseen muistiin, kartoitetun tiedoston ja laiskan virran polut, jotka on kuvattu artikkelissa suoran tiedosto-API:n työnkulku, ovat yleensä parempi työkalu, koska edestakaista viivettä ei alun perinkään ole jaettavaksi
HotPDF on natiivi VCL PDF component Delphille ja C++Builderille, ilman ulkoista DLL-tiedostoa jäsentimelle ja täydellinen lähdekoodi saatavilla. Satunnaiskäyttölähteen API, yhdistelevä kääre ja progressiivisen latauksen aloituspisteet on dokumentoitu sivulla HotPDF Delphi PDF component page