Oletetaan, että öinen Delphi-palvelu tuottaa yhden XLSX-tiedoston asiakasta kohden, muutamia satoja tiedostoja, joista osa on 400 000 riviä leveitä. Profiloi se, ja yllätys on harvoin solujen täyttösilmukka. Se on SaveAs-kutsu. Oletuskirjoittajalla jokainen laskentataulukko sarjallistetaan yhdeksi muistissa olevaksi XML-merkkijonoksi ennen kuin merkkijono pakataan OOXML-zipiin, ja leveässä taulukossa tilapäinen merkkijono voi olla suurempi kuin solumalli, josta se rakennettiin. Siksi työ, joka rakentaa tietonsa mukavasti 800 Mt:ssa, voi piikata yli 2 Gt:n säiliörajan tallennuksen aikana, ja OOM-tappaja jättää virheraportin klo 03.00, kun kukaan ei katso. HotXLS:ssä, losLabin Delphin ja C++Builderin natiivissa laskentataulukkokirjastossa, on ominaisuus, joka on suunnattu juuri tähän piikkiin: StreamingWrite. Sen ympärillä on kaksi muuta vipua, jotka määräävät, pysyykö erätyöntekijä muisti- ja aikabudjetissaan: rivitason kirjoitustakaisinkutsut ja tyylipoolin käyttäytyminen tiukassa silmukassa
Mitä oletustallennuspolku puskuroi ja mitä StreamingWrite muuttaa
Oletusarvoinen XLSX-kirjoittaja suosii yksinkertaisuutta. Se renderöi laskentataulukon XML:n kokonaan ja antaa sitten valmiin merkkijonon zip-pakkaajalle. Se on oikea valinta valtaosalle työkirjoista, joissa koko taulukon XML mahtuu muutamaan megatavuun. Se lakkaa olemasta oikea, kun yhden taulukon sarjallistettu muoto kasvaa satoihin megatavuihin. Laskentataulukon XML on monisanaista: jokainen numeerinen solu maksaa kymmeniä merkkejä merkintäkieltä, ja koko sen sisältävän merkkijonon on oltava yhtenäinen. Muistikaaviossa tunnusmerkkiä on vaikea olla huomaamatta. Pitkä tasainen tasanne rivien täyttyessä, sitten terävä kolmiomainen piikki SaveAs-kutsun aikana ja lopuksi romahdus, kun zip on huuhdeltu
Asettamalla Book.StreamingWrite := True vaihdat SaveAs-metodin laskentataulukkokirjoittajaan, joka lähettää taulukon XML:n suoraan zip-tietovirtaan sitä tuotettaessa. Välimerkkijonoa ei koskaan varata, ja kolmiomainen piikki tasoittuu kohinaksi
Ole tarkka siitä, mitä tämä todellisuudessa tuo, sillä sen liioittelu johtaa vääriin kapasiteettisuunnitelmiin. Lippu muuttaa vain tallennuspolkua. Työkirjan rakentaminen varaa edelleen koko muistissa olevan solumallin, joten täyttövaiheen tasanne on täsmälleen yhtä korkea kuin ennen. Se, mikä katoaa, on sarjallistuspiikki, joka aiemmin kasautui tallennushetkellä tasanteen päälle, ja 400 000 riviä täyttävälle työlle tämä piikki on rutiininomaisesti koko ero muistibudjettiin mahtumisen ja sen ylittämisen välillä. Ominaisuuden oletusarvo on False historiallisen käyttäytymisen säilyttämiseksi, joten käyttöönotto on yksi nimenomainen rivi, jonka kirjoitat tarkoituksella
Massavienti lippu käytössä
Book := TXLSXWorkbook.Create;
try
BoldIdx := Book.Fonts.Add('Calibri', 11, True, False); // pool-indeksi, 0-pohjainen
Sheet := Book.Sheets.Add('Bulk');
for R := 1 to 100000 do
begin
Sheet.Cells[R, 1].Value := R;
Sheet.Cells[R, 2].Value := 'Row ' + IntToStr(R);
Sheet.Cells[R, 3].Value := R * 1.5;
if (R mod 1000) = 0 then
Sheet.Cells[R, 2].FontIndex := BoldIdx + 1; // 1-pohjainen solussa
end;
Book.StreamingWrite := True; // virtaa arkin XML suoraan zipiin
Book.SaveAs('bulk.xlsx');
finally
Book.Free;
end;
Cells[R, C] luo soluja tarvittaessa, mikä pitää silmukan rungon siistinä. Kaksi ruudukon rajaa kannattaa painaa muistiin: 1 048 576 riviä ja 16 384 saraketta, jotka ovat saatavilla nimillä XlsxMaxRow ja XlsxMaxCol. Rajoituksen ylittävä tietosyöte on jaettava omassa koodissasi useille taulukoille. Mikään jatkovaihe ei havaitse ylitystä tai korjaa sitä puolestasi, ja tiedosto päätyy yksinkertaisesti katkaistuksi rajalla
Rivien täyttäminen ilman solukohtaista Variant-ylikuormitusta
Jokainen Cells[R, C].Value-asetus maksaa solun haun ja Variant-muunnoksen. Kymmenellätuhannella rivillä kukaan ei huomaa sitä. Miljoonalla rivillä, joissa jokaisessa on 20 saraketta, kutsukohtaisesta ylikuormituksesta tulee täyttövaiheen hallitseva kustannus, ja profiloija osoittaa suoraan siihen. Erärajapinnat antavat sinun luovuttaa kirjoittajalle kerrallaan kokonaisen rivin. WriteRows ohjaa takaisinkutsua, joka toimittaa yhden rivin kutakin kutsua kohden:
procedure TBulkExporter.FillRow(Sender: TObject; SheetIndex, Row, FirstCol,
LastCol: Integer; var Values: Variant; var Skip: Boolean;
var Cancel: Boolean);
begin
if not FReader.Next then
begin
Cancel := True; // tietolähde tyhjennetty: lopeta siististi
Exit;
end;
Values := VarArrayCreate([FirstCol, LastCol], varVariant);
Values[FirstCol] := FReader.RecordId;
Values[FirstCol + 1] := FReader.CustomerName;
Values[FirstCol + 2] := FReader.Amount;
end;
// täytä rivit 2..100001, sarakkeet A..C, vedä lukijasta
Sheet.WriteRows(2, 1, 100001, 3, FillRow);
Cancel-lippu muuttaa kiinteän rivialueen muotoon "enintään N riviä", mikä on luonteva muoto, kun rivimäärä tulee kyselystä, jonka suorittaminen ei ole vielä päättynyt. Skip on kevyempi kosketus: se jättää yksittäisen rivin tyhjäksi pysäyttämättä ajoa. Solujen täyttämisen lisäksi takaisinkutsu osoittautuu hyväksi paikaksi toiminnallisille huolille, jotka muutoin ruuvattaisiin täyttösilmukkaan kömpelösti. Joka tuhannella rivillä päivittyvä edistymislaskuri, työajastimesta kyseltävä peruutustunnus, lähdetietokannan lukuja rajoittava nopeusrajoitin: kaikki se elää yhdessä paikassa sen sijaan, että se pujotettaisiin solukirjoituskoodin läpi. Lukupuolella ForEachRow ja ForEachCell peilaavat samaa mallia, mikä on tärkeää, kun erätyö sekä kuluttaa että tuottaa suuria tiedostoja
Tyylipoolit palkitsevat nostamisen silmukan ulkopuolelle
XLSX-tyylimalli on joukko jaettuja pooleja. Fonts.Add, Fills.AddSolid ja Borders.Add palauttavat kaikki 0-pohjaisen pooli-indeksin, ja solu viittaa fonttiin tallentamalla indeksin plus yksi FontIndex-ominaisuuteen, jossa nolla on varattu työkirjan oletukselle. +1 näkyy suoraan yllä olevassa massaesimerkissä. Unohda se, ja solu saa hiljaisesti väärän tyylin, koska yhden verran pielessä oleva tyylipoolin indeksi on edelleen kelvollinen indeksi eikä mikään aiheuta virhettä
Seuraava kurinalaisuus on luoda jokainen tyyliobjekti ennen rivisilmukkaa ja viitata sen indeksiin silmukan sisällä. Fonts.Add poistaa kaksoiskappaleet samoista määritelmistä, joten sen kutsuminen kerran riviä kohden vain tuhlaa suoritinaikaa. Alignments.Add on ansa, koska se palauttaa tuoreen merkinnän jokaisella kutsulla. 100 000 rivin silmukassa tämä hautaa styles.xml-tiedoston sadantuhannen päällekkäisen tasaustietueen alle, mikä paisuttaa levyllä olevaa tiedostoa ja hidastaa jokaista myöhempää avausta Excelissä, kun kaksoiskappaleet jäsennetään uudelleen. Rakenna jokainen tyyli kerran silmukan ulkopuolella ja viittaa sen indeksiin niin monta kertaa kuin tarvitset
Tietovirrat, väliaikaishakemistot ja niitä ympäröivä eräsilmukka
Mikään tästä ei vaadi tiedostojärjestelmää. Molemmissa rajapinnoissa on TStream-ylikuormitukset koko IO-pinnallaan, muun muassa metodeissa Open, SaveAs, SaveAsCSV, SaveAsHTML ja SaveAsODS, joten erätyöntekijä voi renderöidä suoraan blob-tallennukseen tai HTTP-vastaukseen sidottuun TMemoryStream-tietovirtaan koskematta koskaan levyyn. Muistettavana on yksi terävä reuna. SaveAs(Stream) kirjoittaa tietovirran nykyisestä sijainnista eikä kelaa sitä jälkikäteen alkuun, joten aseta itse Position := 0, ennen kuin luovutat tietovirran sen toimittavalle taholle, tai kuluttaja lukee nolla tavua. XLS-rajapinta lisää kaksi omaa säädintä. SetTempDir ohjaa BIFF-kirjoittajan väliaikaistiedostot asemalle, jolla on tilaa ja IO-kapasiteettia niiden vastaanottamiseen; tämä on tärkeää palvelimilla, joissa oletusväliaikapolku sijaitsee ahtaalla järjestelmälevyllä. UseSharedFormulas yhdistää toistuvat kaavatekstit jaetuiksi ryhmiksi, mikä pienentää aidosti tiedostokokoa klassisessa raporttimuodossa, jossa yksi kaava kopioidaan koko sarakkeeseen
Eräsilmukka itsessään pysyy tarkoituksella tylsänä:
for FileName in SourceFiles do
begin
Book := TXLSXWorkbook.Create; // uusi instanssi: ei tilavuotoa
try
Book.StreamingWrite := True;
if Book.Open(FileName) <> 1 then
Continue; // yksi huono syöte ei saa tappaa erää
Book.SaveAsCSV(ChangeFileExt(FileName, '.csv'), 0, ',');
finally
Book.Free;
end;
end;
Tuore työkirjaesiintymä tiedostoa kohden maksaa mikrosekunteja ja poistaa kokonaisen luokan tiedostojen välisiä saastumisvirheitä: tiedoston 17 tyyleillä, määritellyillä nimillä ja asiakirjaominaisuuksilla ei ole reittiä vuotaa tiedostoon 18. Epäonnistuneen Open-kutsun ohitus ja jatkaminen ansaitsee paikkansa aivan yhtä paljon, sillä yksi katkennut lataus 600 tiedoston erässä saa maksaa yhden lokirivin eikä loppua ajoa. On myös syytä korostaa, mitä CSV-osuus tarkoituksella ei tee. SaveAsCSV kirjoittaa kaavat kirjaimellisena tekstinä eikä koskaan laske niitä, joten muunnoserän, jonka kuluttajat odottavat laskettuja lukuja, on ajettava Calculate asianomaisille soluille ensin tai aloitettava työkirjoista, joissa on jo aiemman laskennan välimuistitulokset
Samanaikaisuusmalli: yksi työkirja säiettä kohden
Kummankaan rajapinnan objektit eivät ole säieturvallisia, eikä suunnittelu koskaan väittänyt muuta. Koska esiintymien välillä ei ole jaettua globaalia tilaa, skaalaussääntö on yksinkertaisesti yksi työkirja työntekijäsäiettä kohden ilman työkirjan jakamista säikeiden välillä. N työntekijän ryhmä, jossa kukin omistaa oman TXLSXWorkbook-esiintymänsä, skaalautuu lähes lineaarisesti, kunnes muistista tulee katto, ja sille katolle voi antaa luvun: suurin samanaikainen solumalli kerrottuna työntekijöiden määrällä sekä mikä tahansa StreamingWrite-ominaisuuden tasaama tallennusaikainen ylikuormitus. Kun jono syvenee, sovella vastapainetta työjonossa kirjoittajan sisäpuolen sijaan. Nälkiintynyt säie, joka on puoliksi kirjoittanut työkirjan, ei ole tuottanut mitään hyödyllistä, kun taas muutaman sekunnin vapaata työntekijää odottanut työ valmistuu ehjänä
Laajempaa virityskuvaa varten, mukaan lukien jaetut kaavat, grafiikan ohitus lukupuolella ja XLS-kohtaiset säätimet, katso suurten työkirjojen suorituskykyopas. Erätyöt, joiden rivit tulevat suoraan kyselystä, käsitellään erikseen Delphi-raporttien tietokantavientimalleissa
HotXLS kääntyy Delphi- tai C++Builder-palveluusi natiivina Object Pascalina ilman ulkoisia riippuvuuksia; versiot ja lisensointi ovat HotXLS Delphi Component -tuotesivulla