Tehnični članek

Zmogljivost velikih delovnih zvezkov Excel v Delphiju s HotXLS

Ko izvoz s 300.000 vrsticami preseže pomnilniški proračun, navadno okrivimo število vrstic. Toda to število je navadno nedolžno. Dragi deli velikega delovnega zvezka so tisti, ki nastanejo kot stranski učinek: skupni slog, ki zraste za en vnos na celico, ker je bilo oblikovanje dodano znotraj zanke, XML delovnega lista, sestavljen kot en ogromen niz ob shranjevanju, milijon enakih teles formul, shranjenih enega za drugim. HotXLS, izvorna knjižnica Delphi podjetja losLab za datoteke XLS in XLSX, vam za vsak od teh stroškov ponuja poseben vzvod. Noben od njih ni privzeto omogočen, ker vsak spreminja kompromis, zato je poznavanje, kateri vzvod ustreza kateremu simptomu, prava veščina zmogljivosti.

Kje velik delovni zvezek porabi pomnilnik

Obstajata dve različni pomnilniški režimi za razmislek. Med generiranjem model celic v pomnilniku raste z vsako celico, ki se je dotaknete: vrednosti, formati in formule postanejo objekti ali vnosi v skupini. Med shranjevanjem privzeta pot XLSX poleg tega upodob XML vsakega delovnega lista v širok niz, preden ga stisne v vsebnik zip, zato je poraba na vrhuncu model plus serijalizirana oblika največjega lista. Opravilo, ki preživi zanko gradnje in nato propade znotraj SaveAs, zadane drugi režim in ne prvi; popravek enega ne naredi ničesar za drugega.

Velikost datoteke sledi sorodnem pravilu: celice so le eden od prispevkov poleg slogov, skupnih nizov, formul, slik in komentarjev. Revizijsko potovanje s ForEachCell in število zbirk po listih vam pove, kateri vir dejansko prevladuje v problematični datoteki, preden optimizirate napačnega. Ena subtilnost merjenja: Sheet.Cells.Count na strani XLSX poroča število instanciranih celic v redkem pomnilniku, ne površine uporabljenega obsega. List, katerega podatki zasedajo pravokotnik 1000 x 50 s polovico praznih celic, šteje roughly 25.000 in ne 50.000. Ta razlika je pomembna, ko primerjate strankinno "ogromno" datoteko s svojimi referencami, ker se površina uporabljenega obsega in dejanska populacija celic lahko razlikujeta za red velikosti pri redkih finančnih postavitvah.

StreamingWrite popravi pot shranjevanja, ne pot gradnje

Nastavitev TXLSXWorkbook.StreamingWrite := True preklopi SaveAs na pretočni serializator, ki XML delovnega lista neposredno zapisuje v tok zip in odpravlja vmesni niz po listu. Privzeto je False za združljivost obnašanja, njegova vklop pa je sprememba ene vrstice:

Book := TXLSXWorkbook.Create;
try
  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;
  end;
  Book.StreamingWrite := True;   // sheet XML streams into the zip container
  Book.SaveAs('bulk.xlsx');
finally
  Book.Free;
end;

Bodite natančni glede tega, kaj to prinaša: model celic, ki ga zgradi zanka, zavzame natanko toliko pomnilnika kot prej. StreamingWrite poravna konico ob shranjevanju, ki je razlika med paketnim opravilom, ki se zaključi, in tistim, ki ne uspe pri 95 %. Če sama zanka gradnje izčrpa pomnilnik, so naslednja dva vzvoda tista, ki ju potrebujete.

Skupni slogi: dodajte enkrat, znova uporabite indeks

Oblikovanje XLSX v HotXLS temelji na skupni zbirki: Book.Fonts.Add(...), Fills.AddSolid(...) in Borders.Add(...) vrnjejo indeks skupne zbirke z osnovo 0, ki ga celice referencirajo. Klicanje Fonts.Add z enakimi parametri znotraj zanke je podvojevanje, zato zapravi čas in ne prostora. Alignments.Add se obnaša drugače: vrača svež objekt za vsak klic, zato ustvarjanje poravnave na celico linearno povečuje skupno zbirko s številom vrstic. Ena navada pokriva oba primera: razrešite vsak indeks skupne zbirke enkrat, zunaj zanke, in indekse dodelite znotraj nje.

// hoist pool lookups out of the hot loop
HeaderFont := Book.Fonts.Add('Calibri', 11, True, False);   // 0-based pool index
for C := 1 to 24 do
  Sheet.Cells[1, C].FontIndex := HeaderFont + 1;            // cells store 1-based; 0 = default

+ 1 ni tipkarska napaka in njeno pozabljanje je klasična hrošč, ki povzroča simptome: skupne zbirke razdeljujejo indekse z osnovo 0, medtem ko lastnosti na strani celic obravnavajo 0 kot "privzeto", zato je treba vsak indeks skupne zbirke pri dodelitvi povečati za ena. Zamenjate ga s pozabo in vaše glave se tiho upodobijo v privzeti pisavi delovnega zvezka.

Nadomestite promet Variant po celici s povratnimi klici vrstic

Vsaka Sheet.Cells[R, C].Value := X vključuje iskanje-ali-ustvarjanje celice plus dodelitev Variant. Pri nekaj sto tisoč celicah postane ta strošek na dostop merljiv v profilih. HotXLS zagotavlja vmesnike za skupinski povratni klic na obeh fasadah (ForEachCell in ForEachRow za branje, WriteCells in WriteRows za pisanje), ki prenesejo iteracijo v notranjost pogona in vaši kodi posredujejo cele vrstice naenkrat:

procedure TLedgerExport.FillRow(Sender: TObject;
  SheetIndex, Row, FirstCol, LastCol: Integer;
  var Values: Variant; var Skip: Boolean; var Cancel: Boolean);
begin
  if Row > FCount then
  begin
    Cancel := True;     // stop the whole write
    Exit;
  end;
  Values := VarArrayOf([FRows[Row - 1].Account,
                        FRows[Row - 1].PostedOn,
                        FRows[Row - 1].Amount]);
end;

// one engine call instead of hundreds of thousands of property hits
Sheet.WriteRows(1, 1, FCount, 3, FillRow);

Zastavica Skip v povratnem klicu pusti vrstico nedotaknjeno brez prekinitve, Cancel pa predčasno zaključi operacijo, kar je koristno, ko je vir bralnik, katerega dolžino odkrijete sproti. Združite WriteRows za gradnjo s StreamingWrite za shranjevanje in pot generiranja nima več vroče točke na celico.

Vzvodi na strani branja za fasado XLS

Velike zastarele datoteke .xls imajo lastno zbirko orodij. _DisableGraphics := True pred Open v celoti preskoči razčlenjevanje plasti risb, kar pospeši nalaganje delovnih zvezkov z leti nakopičenih oblik in vgrajenih slik. Omejitev je stroga: plast risb je nato odsotna iz modela, zato shranjevanje takšnega delovnega zvezka zapiše datoteko brez risb. To zastavico rezervirajte za samo-bralna analitična opravila. SetTempDir preusmeri začasne datoteke zapisovalnika BIFF, kar je pomembno na strežnikih, kjer ima privzeta lokacija za začasne datoteke kvoto ali se nahaja na počasnem pomnilniku. UseSharedFormulas združi ponavljajoča se telesa formul v zapise deljenih formul in zmanjša datoteke, kjer se stolpec formule ponavlja šestdeset tisoč vrstic navzdol.

Bralne zanke nad podatki XLS imajo past indeksiranja, ki je vredna izpostavitve, ker podvoji delo pri defensivnem ravnanju in pokvari rezultate, ko jo zamudimo: UsedRange poroča meje FirstRow, LastRow, FirstCol in LastCol z osnovo 0, medtem ko je Cells.Item[Row, Col] z osnovo 1. Pregled, ki hodi po uporabljenem obsegu, mora pri dostopu do celice dodati ena vsaki koordinati, kot v Cells.Item[Row + 1, Col + 1], sicer bere mrežo, premaknjeno diagonalno za eno celico. Povratni klic ForEachCell to neskladje povsem obide, kar je še en razlog za prednost pri celih preiskovanjih lista.

Pred nalaganjem preverite datoteke

Najcenejša operacija z velikim delovnim zvezkom je tista, ki se ji izognemo. GetSheetNames na obeh fasadah navede delovne liste datoteke brez nalaganja podatkov celic. Implementacija XLSX bere samo manifest delovnega zvezka znotraj zipa in izrecno pusti instanco delovnega zvezka neposeljeno, fasada XLS pa preneha pregledovati pri prvi meji podtoka. To ga naredi za pravo preverjanje pred letom za vprašanje "kateri list naj cilja to uvozno opravilo", CanReadEncrypted pa odgovori na "je to šifriran vsebnik" pred obsojenem poskusom Open.

Names := TStringList.Create;
Book := TXLSXWorkbook.Create;
try
  if Book.GetSheetNames('big-unknown.xlsx', Names) <= 0 then
    raise Exception.Create('cannot enumerate sheets');   // failure clears the list
  // pick the target sheet, then decide whether a full Open is worth it
finally
  Book.Free;
  Names.Free;
end;

Upoštevajte konvencijo vrnjene kode: te sondne funkcije signalizirajo neuspeh z vrednostmi, manjšimi ali enakimi nič, in izpraznijo izhodni seznam, zato preverite <= 0 namesto primerjave z eno določeno vrednostjo uspeha.

Prilagoditev pristopa opravilu

Za brez nadzora delujoče cevovode, ki zapored generirajo veliko velikih datotek, dve navadi zaokrožita sliko. Objekti delovnih zvezkov niso varni za niti pri skupni rabi, toda nič ne preprečuje enega neodvisnega delovnega zvezka na delovno nit, kar paralelizira skupinsko konverzijo. Ko izhod gre na HTTP in ne na disk, prekoračitve shranjevanja TStream v kombinaciji s StreamingWrite zagotovijo, da se velik odgovor nikoli ne materializira kot začasna datoteka. Velja eno operativno opombo: shranjevanje toka piše od trenutnega položaja brez navijanja nazaj, zato pred posredovanjem toka okvirju odziva nastavite Position := 0. Članek o pretočnem pisanju in paketnih opravilih razvija ta vzorec na strani strežnika, članek o izvozu baz podatkov pa prikazuje, kje se ti vzvodi ujemajo v poročilu, ki ga poganja nabor podatkov.

Na koncu za vsako družino poročil hranite eno najslabšo referenčno napravo in jo časovno merite v CI. Regresije zmogljivosti pri generiranju dokumentov se redko oglasijo same. Slog, dodan znotraj zanke, ali sonda, nadomeščena s polnim Open, ne spremeni ničesar funkcionalno in nočna skupina preprosto traja štirideset minut dlje. Časovni test na reprezentativni napravi s pol milijona celic to drso spremeni v rdeč gradbeni postopek in ne v operativni incident.

Evalvacijski gradniki, demo projekti s primerom skupinskega generiranja in celotna referenca API so na voljo na strani HotXLS Component.