PDF-jäsentimen (parser) ensimmäinen (first) hyödyllinen (useful) luku (read is at the) on tiedoston (file) väärässä päässä (wrong end). Formaatti sijoittaa startxref-osoittimen (pointer in the) viimeisiin tavuihin (final bytes), joten 1,8 GB:n arkiston käsittely (processing) alkaa haulla (with a seek to the) häntään (tail), yhden kilotavun luvulla (one-kilobyte read), ja sitten (then a) hypyllä (hop to wherever the) minne tahansa (wherever), missä ristiviittaustaulukko (cross-reference table) sanoo (says the) asiakirjahakemiston (document catalog) sijaitsevan (lives). Siitä lähtien (From there the) jäsennys (parse) on satunnaiskävely (random walk across the) koko tavualueen (byte range) yli. Kaikki se (Everything buffered IO is good at), missä puskuroitu IO on hyvää — peräkkäinen luku eteenpäin (sequential read-ahead behind the) tiedosto-osoittimen perässä (file pointer) — on (is) suunnattu (aimed at a) työkuormaan (workload), jota PDF:llä ei ole (does not have)
Tämän artikkelin ensimmäinen versio (first version of this article) väitti (claimed a), että muistikartoitettu (memory-mapped) tiedosto (file) ratkaisee (solves the) 32-bittisen muistin loppumisen (out-of-memory failure that), johon TMemoryStream törmää (hits on a) 2 GB:n syötteellä. Tämä väite (That claim is wrong) on väärä, ja se tapa (the way), jolla (it is wrong points at the) se (it is wrong) on väärä, osoittaa (points at the real fix) todelliseen (real) korjaukseen (fix): liukuvaan kartoitusikkunaan (sliding mapping window). Seuraavassa on pääsykuvio (access pattern), korjattu (corrected) 32-bittinen (32-bit story) tarina käännettävällä (compilable) ikkunoidulla kartoittajalla (windowed mapper), ja (and the) järjestelmäkutsuaritmetiikka (syscall arithmetic on a) 1,8 GB:n testitiedostolla (test file), jossa on (with) 300 000 objektia
Miksi PDF-asettelu (layout) voittaa puskuroidut luvut (buffered reads)
Kolme rakenteellista faktaa (structural facts shape the) muokkaavat IO-kuviota (IO pattern). Ensinnäkin (First), navigointi (navigation is) on siirtymäohjattua (offset-driven): ristiviittaustaulukko (cross-reference table maps every) kartoittaa jokaisen objektinumeron absoluuttiseen (absolute byte position) tavusijaintiin (byte position, and nothing requires those), eikä (and nothing) mikään (nothing requires those positions to be) vaadi noiden sijaintien (positions) olevan (ordered) järjestyksessä (ordered). Vuosien inkrementaalisten päivitysten jälkeen objekti 4102 voi (can) sijaita siirtymän (offset) 1,6 GB kohdalla (while), kun taas (while) objekti 4103 sijaitsee 30 KB:n kohdalla. TFileStream-silmukka (loop turns every) muuttaa jokaisen (every fetch into a) noudon (fetch) hauksi (Seek) ja luvuksi (Read), kahdeksi (two) ytimen siirtymäksi (kernel transitions), puskurin avulla (with a buffer that), joka ei (contributes nothing because the) osallistu (contributes nothing) millään tavalla (nothing), koska (because) seuraava (next fetch) nouto on satojen megatavujen (megabytes) päässä (away)
Toiseksi, objektivirrat (object streams) (ISO 32000-1 §7.5.7) pakkaavat kymmeniä tai (or) satoja (hundreds of) pieniä sanakirjoja yhteen (into one) purettuun (deflated) säiliöön (container). Yhden 300 tavun sivusanakirjan (page dictionary) noutaminen voi (can mean reading and inflating a) tarkoittaa (mean reading and) 100 KB:n klusterin (cluster) lukemista (reading) ja (and inflating a) purkamista. Kääntöpuoli (The flip side): objektit, jotka (that) kirjoitetaan yhdessä, luetaan usein (tend to be read) yhdessä (together), joten klusterin kokoiseksi (sized to the cluster) mitoitettu (sized) puskuri (buffer) palvelee (serves the next dozen fetches for) seuraavaa tusinaa noutoa ilmaiseksi (for free) — muodon hyödynnettävin (most exploitable) säännönmukaisuus (regularity in the format)
Kolmanneksi, linearisointi (linearization). Linearisoidun tiedoston alkuun on ladattu (front-loads the) ensimmäinen (first) sivu ja vihjetaulukko (hint table so consumers can read it front to back), jotta kuluttajat voivat lukea sen (it) alusta loppuun (front to back). Gigatavuluokan (Gigabyte archives are almost never linearized) arkistot eivät (are almost never linearized) lähes (almost never) koskaan (never linearized) ole linearisoituja: linearisoinnin tuhoavat (linearization is destroyed by the same) samat (same) inkrementaaliset päivitykset ja yhdistämiset (merges that made the file large), jotka tekivät (made the) tiedostosta (file) suuren (large). Suunnittele (Plan for the) vihamielistä tapausta (hostile case) varten: pitkiä hyppyjä (long hops), ei (no) järjestystä, tiedostoon astutaan (entry) häntä edellä (tail-first)
32-bittinen (The 32-bit) tarina, korjattuna
32-bittisellä (A 32-bit Windows process has) Windows-prosessilla on 2 GB käyttäjän (user) osoiteavaruutta (address space, and), ja nollan (zero asks for one contiguous reservation the) tavumäärällä (byte count of) kutsuttu MapViewOfFile pyytää (asks for) yhden yhtäjaksoisen (contiguous) varauksen (reservation), joka on (the size of the) tiedoston koon suuruinen. 2 GB:n (For a 2 GB input that) syötteellä tämä (that) varaus (reservation) ei voi onnistua (cannot succeed): EXE:n, hajallaan (scattered) olevien DLL-tiedostojen ja (and thread) säikeiden pinojen (thread stacks) jälkeen (after the EXE) tyypillisen (in a typical 32-bit Delphi process) 32-bittisen Delphi-prosessin suurin (largest) vapaa (free) yhtäjaksoinen lohko (contiguous block) sijaitsee (sits somewhere between) jossain (somewhere between) 700 MB:n ja 1,4 GB:n välissä. Kutsu epäonnistuu (fails with) ERROR_NOT_ENOUGH_MEMORY -virheeseen, samaan seinään (same wall), johon (wall) TMemoryStream.LoadFromFile törmää (hits), se vain siirtyy (just moved from) varatusta (committed RAM to) RAM-muistista osoiteavaruuden (address-space) varaukseen (reservation). Koko (A full-file) tiedoston (file) kartoitus (mapping is no fix on 32-bit) ei ole mikään (no) korjaus (fix on 32-bit) 32-bittisessä järjestelmässä, se on (just the same) täsmälleen (just) sama (same failure behind better-sounding API names) epäonnistuminen (failure) paremmalta kuulostavien (better-sounding) API-nimien takana
Korjaus (The fix is) on sen erottaminen (separating the two things a mapping does), mitä kahta asiaa kartoitus (mapping) tekee. CreateFileMapping luo (creates the) osio-objektin (section object) eikä (and) maksa yhtään osoiteavaruutta (costs no address space at all), olipa tiedoston koko (whatever the file size) mikä (whatever) tahansa (whatever the file size). Vain MapViewOfFile kuluttaa (spends) osoiteavaruutta (address space), eikä mikään pakota (and nothing forces it to map the whole section) sitä kartoittamaan koko (whole) osiota: se ottaa (it takes a) 64-bittisen aloitussiirtymän (starting offset) ja näkymän pituuden (view length). Luo osio (Create the section once) kerran, kartoita 64–256 MB:n näkymä jäsenneltävän alueen päälle (over the region being parsed), pura kartoitus (unmap before sliding on) ennen eteenpäin liukumista (sliding on): osoiteavaruuden (address-space) kustannus (cost) on (is one) yksi ikkuna (window), ei (not) yksi tiedosto. Yksi (One constraint) rajoite (constraint): näkymän siirtymien on oltava (must be multiples of) SYSTEM_INFO.dwAllocationGranularity -arvon monikertoja (multiples), käytännössä 64 KB, joten (so a) pyyntö (request for offset) siirtymälle 1 000 000 pyöristetään alaspäin (gets rounded down to) arvoon 983 040 ja soittajan (caller's pointer adjusted forward by the) osoitinta (pointer) säädetään eteenpäin erotuksen (difference) verran
Liukuva ikkuna -kartoittaja (A sliding-window mapper in) Delphissä
Alla oleva luokka (class below wraps the whole discipline) käärii koko (the whole) tekniikan (discipline): yhden osio-objektin (one section object), yhden elävän (live) näkymän, rakeisuuden uudelleenkohdistamisen (granularity realignment, and reads that cross a window boundary handled by), ja (and) lukemiset (reads that), jotka (that) ylittävät ikkunan rajan (window boundary handled by growing that), käsitellään (handled by) kasvattamalla (growing that) tätä (that one view) yhtä näkymää sen sijaan, että ommeltaisiin (instead of stitching two) kaksi (two) yhteen (stitching two)
uses
Winapi.Windows, System.SysUtils;
type
TWindowedFileMapper = class
private
FFile: THandle;
FMapping: THandle;
FFileSize: Int64;
FGranularity: DWORD; // SYSTEM_INFO.dwAllocationGranularity
FWindowSize: NativeUInt; // oletusnäkymän (default view size) koko
FViewBase: PByte; // nykyisen (current view) näkymän kanta (base) (kohdistettu)
FViewOffset: Int64; // tiedoston (file offset) siirtymä, jota (file offset FViewBase corresponds to) FViewBase (FViewBase) vastaa
FViewSize: NativeUInt; // tavuja (bytes) kartoitettu (mapped) nykyisessä näkymässä (current view)
procedure Unmap;
public
constructor Create(const FileName: string;
WindowSize: NativeUInt = 64 * 1024 * 1024);
destructor Destroy; override;
function Map(Offset: Int64; Size: NativeUInt): PByte;
procedure ReadBytes(Offset: Int64; var Buffer; Count: NativeUInt);
property FileSize: Int64 read FFileSize;
end;
constructor TWindowedFileMapper.Create(const FileName: string;
WindowSize: NativeUInt);
var
Info: TSystemInfo;
begin
inherited Create;
FFile := CreateFile(PChar(FileName), GENERIC_READ, FILE_SHARE_READ, nil,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if FFile = INVALID_HANDLE_VALUE then
RaiseLastOSError;
if not GetFileSizeEx(FFile, FFileSize) then
RaiseLastOSError;
// Osio-objekti ei varaa osoiteavaruutta, tiedoston koosta riippumatta
FMapping := CreateFileMapping(FFile, nil, PAGE_READONLY, 0, 0, nil);
if FMapping = 0 then
RaiseLastOSError;
GetSystemInfo(Info);
FGranularity := Info.dwAllocationGranularity; // käytännössä 64 KB
FWindowSize := WindowSize;
end;
destructor TWindowedFileMapper.Destroy;
begin
Unmap;
if FMapping <> 0 then CloseHandle(FMapping);
if FFile <> INVALID_HANDLE_VALUE then CloseHandle(FFile);
inherited;
end;
procedure TWindowedFileMapper.Unmap;
begin
if FViewBase <> nil then
begin
UnmapViewOfFile(FViewBase);
FViewBase := nil;
FViewSize := 0;
end;
end;
function TWindowedFileMapper.Map(Offset: Int64; Size: NativeUInt): PByte;
var
AlignedOffset: Int64;
Delta, MapSize: NativeUInt;
begin
if (Offset < 0) or (Offset + Int64(Size) > FFileSize) then
raise ERangeError.CreateFmt(
'Kartoituspyyntö kohdassa %d koolle %d on tiedoston ulkopuolella',
[Offset, Int64(Size)]);
// Nopea (Fast path) reitti: pyydetty alue istuu (already sits inside the live view) jo elävän (live) näkymän sisällä (inside)
if (FViewBase <> nil) and (Offset >= FViewOffset) and
(Offset + Int64(Size) <= FViewOffset + Int64(FViewSize)) then
Exit(FViewBase + NativeInt(Offset - FViewOffset));
Unmap; // liukuminen: älä koskaan pidä kahta näkymää yhtaikaa
// Näkymien (Views must start on an allocation-granularity boundary) on alettava allokaation rakeisuuden rajalta
AlignedOffset := Offset - (Offset mod FGranularity);
Delta := NativeUInt(Offset - AlignedOffset);
MapSize := FWindowSize;
if MapSize < Size + Delta then // pyyntö hajallaan ikkunan lopun yli:
MapSize := Size + Delta; // kasvatetaan tätä yhtä näkymää kattamaan se
if AlignedOffset + Int64(MapSize) > FFileSize then
MapSize := NativeUInt(FFileSize - AlignedOffset); // rajoitetaan EOF:iin (EOF)
FViewBase := MapViewOfFile(FMapping, FILE_MAP_READ,
DWORD(AlignedOffset shr 32), DWORD(AlignedOffset and $FFFFFFFF),
MapSize);
if FViewBase = nil then
RaiseLastOSError;
FViewOffset := AlignedOffset;
FViewSize := MapSize;
Result := FViewBase + NativeInt(Delta);
end;
procedure TWindowedFileMapper.ReadBytes(Offset: Int64; var Buffer;
Count: NativeUInt);
begin
Move(Map(Offset, Count)^, Buffer, Count);
end;
Kaksi yksityiskohtaa (Two details) kantaa (carry the) painon (weight). Map-funktion (Map) alussa oleva nopea reitti (fast path at the top of) palauttaa (returns a) osoittimen (pointer) ilman ytimen siirtymää (no kernel transition), kun pyydetty (when the requested) alue sijaitsee (range already sits inside the live view) jo elävän näkymän sisällä; objektivirtojen (object-stream clustering this) klusteroinnin (clustering) ansiosta tämä on (is the) yleisin tapaus (common case and where the savings come from), ja tästä säästöt tulevat (come from). Ja pyyntö (request that), joka hajautuu (straddles the end of the) oletusikkunan (default window) lopun ylitse (end of the), kasvattaa (grows) MapSize-arvoa kyseiselle (for that one) yhdelle (one) näkymälle (view) sen sijaan, että kaksi (two) ommeltaisiin yhteen, mikä (which) pitää (keeps) ReadBytes-funktion (ReadBytes) yksirivisenä (one-liner and) ja soittajat vapaina (callers free of) osittaisista (partial-read loops) lukusilmukoista (partial-read loops)
Ikkunan koko (Window size) on (is a forgiving knob) anteeksiantava säädin (knob): 64 MB:n arvolla (at 64 MB a full sweep of a) 1,8 GB:n tiedoston täysi läpikäynti vaatii 29 näkymää (29 views), 256 MB:n arvolla niitä on (it is 8) 8 (8), mutta jokainen varaus (reservation) on vaikeampi (is harder to place in a) sijoittaa pirstoutuneeseen (fragmented) 32-bittiseen tilaan, ja (and) alle noin 16 MB:n arvoilla (below about 16 MB) paljon (hop-heavy) hyppyjä sisältävät (hop-heavy) tiedostot rekartoittavat (remap) tarpeeksi usein (often enough to notice), jotta sen huomaa (notice). Missä tahansa 64–256 MB:n välillä (Anywhere in the 64 to 256 MB range), kartoitusliikenne (map traffic) on (is statistical noise) tilastollista kohinaa (noise)
Järjestelmäkutsujen (syscalls) laskeminen (Counting the)
Nyt (Now the) aritmetiikkaan (arithmetic). Testitiedosto (Test file): 1,8 GB, 300 000 epäsuoraa (indirect objects) objektia, joissa on keskimäärin (averaging about 600 bytes) noin (about) 600 tavua hyötykuormaa (payload). Objektikohtainen (per-object) jäsennin noutaa jokaisen SetFilePointerEx-kutsulla ja (plus a) 4 KB ReadFile-kutsulla: 600 000 ytimen (kernel) siirtymää (transitions). Välimuistissa oleva (cached read) lukujärjestelmäkutsu kuluu pyöreästi 1,5 µs nykyisellä (on current x64 hardware) x64-laitteistolla (hardware), joten se on 600 000 × 1,5 µs ≈ 0,9 sekuntia puhdasta ytimen (pure kernel) yleiskustannusta (overhead before) ennen (before) yhdenkään tavun (a single byte) jäsentämistä (parsing) — lämpimän (warm-cache best case) välimuistin paras (best) tapaus (case). Kylmänä (Cold), jokainen hyppy on (each hop is a) laitetoiminto (device operation): NVMe 4 KB:n satunnaislukujen (random reads) ~20 µs efektiivisellä latenssilla (latency), 300 000 tällaista maksaa (cost) noin 6 sekuntia (seconds of) laiteaikaa (device time); SATA-luokan tallennustilalla minuutteja (minutes)
Luvut siirtävät (move the) myös väärää (wrong) dataa (data): 300 000 × 4 KB puskee (pushes) 1,2 GB käyttäjän puskurien (user buffers) läpi toimittaakseen pyöreästi (roughly) 180 MB hyötykuormaa — kuusinkertainen (six-fold) vahvistuminen (amplification), jokainen tavu kopioituna ytimestä (kernel to) käyttäjälle (user)
Objektivirtaklustereiden kokoiseksi (sized to the object-stream clusters is the) mitoitettu ennakoivan luvun (read-ahead) puskuri on ensimmäinen rehellinen (honest) parannus (improvement): yksi 256 KB:n luku per (per) klusteri (cluster) yhden luvun sijaan per (instead of one per) objekti (object) leikkaa (cuts the) siirtymäyhteyksiä yhdellä tai kahdella suuruusluokalla. Se on myös oikea työkalu, kun (where) kartoitus (mapping) on (is) hankalaa (awkward), yleensä (usually) verkkolevyillä (network shares)
Ikkunoitu (windowed mapper) kartoittaja menee pidemmälle (goes further). Täysi (full) läpikäynti on (is) 29 MapViewOfFile ja (and) 29 UnmapViewOfFile kutsua (calls), 58 nimenomaista (explicit) siirtymää 600 000 vastaan (against). Todellinen xref-ohjattu jäsennys ei (is not a) ole puhdas läpikäynti (clean sweep), mutta nopea (fast) reitti imee (absorbs) jokaisen noudon (every fetch) elävän ikkunan (live window) sisällä (inside); metadatan indeksointivaihe (metadata-indexing pass) testitiedoston (test archive) yli asettui (settled at a) muutamaan sataan (few hundred) rekartoitukseen (remaps). Kartoitus (Mapping) ei poista ytimen tekemää työtä: se muuttaa nimenomaiset järjestelmäkutsut sivuvirheiksi (page faults), jotka muistinhallinta (memory manager resolves in) ratkaisee monisivuisissa (multi-page) klustereissa (clusters), suoraan tiedostovälimuistista (file cache with no) ilman käyttäjätilan (user-space copy) kopiointia (copy), ja koskemattomat alueet (regions never touched) eivät (cost nothing) maksa mitään (cost nothing). Päästä (End to end) päähän indeksointivaihe lyheni 23 sekunnista kylmänä ja 7,1 sekunnista lämpimänä objektikohtaisilla (per-object) luvuilla (reads to) 6,5 sekuntiin kylmänä ja 1,9 sekuntiin lämpimänä kartoittajan (mapper) kanssa (with the mapper); jäljelle (what remains is) jää vain zlib-purku (inflate), ei IO (IO)
Mihin FILE_FLAG_NO_BUFFERING sopii
FILE_FLAG_NO_BUFFERING ohittaa järjestelmävälimuistin (bypasses the system cache in) vastineeksi tarkoista kohdistussäännöistä (exchange for hard alignment rules): siirtymät, pituudet ja (and buffer addresses) puskuriosoitteet (addresses all) kaikki sektorikohdistettuna (sector-aligned). Se ansaitsee paikkansa yksisuuntaisissa (single-pass) peräkkäisissä (sequential) töissä (jobs), jotka muutoin (would otherwise flood the cache with) hukuttaisivat (flood the cache with) välimuistin (cache with) tavuilla (bytes), joita kukaan ei (nobody reads twice) lue kahdesti (reads twice) — eräajona (batch) tapahtuva (re-serialization that rewrites the whole archive) uudelleensarjallistaminen, joka (that rewrites the) kirjoittaa koko arkiston uudelleen, tai (or a linearization pass over) linearisointivaihe valmistuneelle (finished output) tulosteelle (finished output). 4–8 MB:n kohdistetuilla (aligned) puskureilla (buffers) se (it approaches device sequential) lähestyy (approaches) laitteen (device) peräkkäistä (sequential) kaistanleveyttä (bandwidth without polluting the) saastuttamatta (polluting the) välimuistia (cache)
Se (It is exactly wrong for parsing) on (is) tismalleen (exactly) väärin (wrong for) jäsentämisessä (parsing). Satunnaiset (Random) xref-hypyt puskuroimattoman (unbuffered) kahvan (handle) läpi kääntävät jokaisen 300 tavun sanakirjan (dictionary fetch into a) noudon kokonaiseksi fyysiseksi lukutoimenpiteeksi, jolla (with no cache to absorb the) ei ole välimuistia (no cache to absorb the) toisen vierailun vastaanottamiseen (absorb the second visit) — ja PDF-jäsennys käy alueilla toistuvasti (revisits regions constantly), koska eri sivut (pages) ratkeavat (resolve into the) samoihin objektivirtoihin (object streams). Puskuroimatonta (Unbuffered) IO:ta peräkkäiseen uudelleenkirjoitukseen (rewrite), kartoitettua (mapped) tai puskuroitua IO:ta satunnaiseen jäsennykseen; lippu (flag) on kahvakohtainen (per-handle), joten yksi (one) putki (pipeline can) voi pitää molempia samassa (both on the same file) tiedostossa
64-bittinen (64-bit), työjoukot (working sets) ja (and the) kirjoituspuoli (write side)
64-bittisessä (On a 64-bit build the address-space objection disappears) koontiversiossa (build) osoiteavaruuden (address-space) rajoitus (objection disappears) katoaa: ohita tiedoston (file size) koko (size as the) ikkunana, jolloin yllä oleva luokka rappeutuu (degenerates into a) yhdeksi, täydeksi (full mapping) kartoitukseksi (full mapping). Pulma pitkään jatkuvissa (long-running) palveluissa (services): vain (read-only file-backed pages charge no commit, so commit counters stay calm) lukukäyttöön tarkoitetut tiedostopohjaiset (file-backed pages charge no commit) sivut (pages charge no commit) eivät veloita sitoumusta (commit counters stay calm), joten (so) sitoumuslaskurit (commit counters stay calm) pysyvät (stay calm) rauhallisina (calm), mutta jokainen kosketettu (touched) sivu liittyy (joins the) työjoukkoon (working set); jäsennä (parse) suurimman osan 1,8 GB:stä, ja (and the working set grows to match) työjoukko (working set) kasvaa (grows to) sen mukaan (to match, evicting everything else), poistaen kaiken muun (evicting everything else). Rajoitetut (Bounded) ikkunat asettavat katon (ceiling on that) sille, joten liukuva kuvio (sliding pattern stays the) pysyy oikeana oletusarvona, vaikka osoiteavaruutta (address space is free) olisikin vapaana (free)
Kirjoituspuolella (On the write side) halvin IO on se IO, jota (never issued) ei (never) koskaan suoriteta (issued). PDF:n (PDF's incremental update mechanism) inkrementaalinen päivitysmekanismi (ISO 32000-1 §7.5.6) liittää muutetut (changed objects and a new) objektit ja (and a new cross-reference) uuden (new) ristiviittausosion (cross-reference section after the original bytes, which never move) alkuperäisten (original) tavujen (bytes, which) perään (after the), jotka eivät koskaan (never) liiku (move). Yhden (one) sivun leimaaminen 1,8 GB:n arkistoon (archive) liittää kymmeniä (tens of) kilotavuja (kilobytes); kokonainen (full rewrite) uudelleenkirjoitus siirtää (moves) kaiken 1,8 GB:n, mikä on viiden (five orders of magnitude apart) suuruusluokan päässä toisistaan (apart), ja liittäminen (append) on (is) pelkkää (pure sequential output at the) peräkkäistä (sequential) tulostetta hännässä (tail)
Mihin (Where the losLab libraries fit) losLab-kirjastot (libraries) sopivat (fit)
Molemmat losLab PDF -kirjastot tarjoavat tämän tavan API-pinnalla. HotPDF Direct File API lukee (reads page counts and structure through a) sivumääriä (counts and structure through a) ja (and structure through a) rakennetta tiedostokahvan (file handle without building the) läpi rakentamatta objektipuuta (without building the object tree), kopioi ja purkaa salauksen tiedostotasolla (file level) ja kirjoittaa deltamuutokset (deltas) BeginIncrementalUpdate -kutsun kautta — pelkästään liittävän strategian (append-only strategy above), pakattuna (packaged). PDFlibPas noudattaa samaa reittiä (same route with its) sen Direct Access (Direct Access layer) -kerroksen avulla: se (a streaming reader that) on virtaava lukija (streaming reader that walks the cross-reference), joka (that walks the) kävelee ristiviittaustaulukon (cross-reference table in place) läpi (in place), noutaa objektit laiskasti (lazily), poimii (extracts page ranges) sivualueet (page ranges file to file) tiedostosta tiedostoon (file to file, and persists edits as incremental revisions) ja säilyttää muokkaukset inkrementaalisina versioina. Jos olet kirjoittamassa (writing your own) omaa (own) jäsennintä (parser), kartoittajaluokka (mapper class is yours to take) on sinun (yours) otettavissasi; jos ajat (running a document pipeline, let the) asiakirjaputkea (document pipeline), anna (let the library keep the) kirjaston pitää ikkuna (window) aisoissa (honest)
Huomautus: Optimoitu IO-käsittely gigatavuluokan asiakirjoille on sisäänrakennettu (built directly into the) HotPDF VCL Componentiin Delphille (Delphi and) ja C++Builderille