C #: File.ReadLines () vs File.ReadAllLines () - og af hverju ætti mér að vera sama?

Fyrir nokkrum vikum rakst ég og tvö af liðunum sem ég er að vinna með umræður um skilvirkar leiðir til að vinna úr stórum textaskrám.

Þetta kallaði fram nokkrar aðrar fyrri umræður sem ég átti í fortíðinni um þetta efni og einkum um notkun ávöxtunar ávöxtunar í C ​​# (sem ég mun líklega tala um í framtíðar bloggfærslu). Svo, ég hélt að það væri góð áskorun að sýna fram á hvernig C # getur kvarðað á áhrifaríkan hátt þegar kemur að vinnslu á stórum klumpum gagna.

Áskorunin

Svo að vandamálið sem fjallað er um er:

  • Segjum sem svo að það sé stór CSV skrá, segjum ~ 500MB fyrir byrjendur
  • Forritið verður að fara í gegnum hverja línu skráarinnar, para hana og gera nokkra kort / draga úr útreikningum

Og spurningin á þessum tímapunkti í umræðunni er:

Hver er skilvirkasta leiðin til að skrifa kóðann sem er fær um að ná þessu markmiði? Þó að farið sé að:
i) lágmarka magn notaðs minni og
ii) lágmarka kóðalínur forritsins (að sjálfsögðu að hæfilegu marki)

Til rökstuðnings gætum við notað StreamReader, en það myndi leiða til þess að skrifa fleiri kóða sem þarf og í raun hefur C # þegar File.ReadAllLines () og File.ReadLines () þægindaaðferðirnar. Þannig að við ættum að nota þau!

Sýndu mér kóðann

Þess vegna skulum við líta á forrit sem:

  1. Tekur textaskrá sem innslátt þar sem hver lína er heiltala
  2. Reiknar summan af öllum tölunum í skránni

Af þessu dæmi sleppum við ansi staðfestingarskilaboðum :-)

Í C # er hægt að ná þessu með eftirfarandi kóða:

var sumOfLines = File.ReadAllLines (filePath)
    .Veldu (lína => millifærsla (lína))
    .Summa()

Frekar einfalt, ekki satt?

Hvað gerist þegar við matum þetta forrit með stórum skrá?

Ef við keyrum þetta forrit til að vinna úr 100MB skrá, þá er það það sem við fáum:

  • 2GB af RAM minni neytt minni til að ljúka þessari tölvuvinnslu
  • Fullt af GC (hver guli hlutur er GC keyrsla)
  • 18 sekúndur til að ljúka framkvæmdinni
BTW, að fæða 500MB skrá í þennan kóða olli því að forritið hrundi með OutOfMemoryException Gaman, ekki satt?

Við skulum reyna File.ReadLines () í staðinn

Við skulum breyta kóðanum til að nota File.ReadLines () í stað File.ReadAllLines () og sjáum hvernig það gengur:

var sumOfLines = File.ReadLines (filePath)
    .Veldu (lína => millifærsla (lína))
    .Summa()

Þegar við keyrum það fáum við núna:

  • 12MB af vinnsluminni neytt, í stað 2GB (!!)
  • Aðeins 1 GC keyrsla
  • 10 sekúndum til að ljúka, í stað 18

Af hverju er þetta að gerast?

TL; DR lykilmunurinn er sá að File.ReadAllLines () er að smíða streng [] sem inniheldur hverja línu skrárinnar, sem þarf nóg minni til að hlaða alla skrána; öfugt við File.ReadLines () sem matar forritið hverja línu í einu og þarfnast aðeins minni til að hlaða eina línu.

Í smáatriðum:

File.ReadAllLines () les alla skrána í einu og skilar streng [] þar sem hvert atriði fylkisins samsvarar línu í skránni. Þetta þýðir að forritið þarf eins mikið minni og stærð skráarinnar til að hlaða innihaldið úr skránni. Plús nauðsynlega minni til að para ÖLL strengjaþáttina til að int og reikna síðan summan ()

Á hinni hliðinni, File.ReadLines () býr til upptalningu á skránni og les hana línu fyrir línu (reyndar með StreamReader.ReadLine ()). Þetta þýðir að hver lína er lesin, umbreytt og bætt við hluta summan í línu-vera-línu háttur.

Niðurstaða

Þetta efni kann að virðast eins og smávægilegt útfærsluatriði, en það er í raun mjög mikilvægt vegna þess að það ákvarðar hvernig forrit verður í stærðargráðu þegar það er gefið með stórt gagnasett.

Það er mikilvægt fyrir forritara að geta spáð fyrir um þessar tegundir af aðstæðum, því maður veit aldrei hvort einhver ætlar að koma með mikið inntak sem ekki var gert ráð fyrir á þróunarsviðinu.

LINQ er einnig nógu sveigjanlegt til að takast á við þessi tvö atburðarás óaðfinnanlega og veita framúrskarandi skilvirkni þegar þau eru notuð með kóða sem veitir „streymi“ um gildi.

Þetta þýðir að ekki þarf allt að vera Listi eða T [] sem þýðir að allt gagnasettið er hlaðið í minni. Með því að nota IEnumerable gerum við kóða okkar almenna til að nota með aðferðum sem veita allt gagnasettið í minni eða sem gefur gildi í „streymi“ ham.