Back to Question Center
0

Kuinka lukea suuria tiedostoja PHP: llä (ilman palvelimen tappamista) Kuinka lukea suuria tiedostoja PHP: llä (ilman palvelimen tappamista) Aiheeseen liittyviä artikkeleita: Drupal Development Semalt

1 answers:
Kuinka lukea suuria tiedostoja PHP: llä (ilman palvelimen tappamista)

Epäilemättä usein, että me, kuten PHP-kehittäjinä, täytyy huolehtia muistinhallinnasta. PHP-moottori on täynnä puhdistustöitä jäljessä, ja lyhytikäisten toteutusyhteyksien verkkopalvelimallissa jopa viivästyneellä koodilla ei ole pitkäkestoisia vaikutuksia.

On harvinaisia ​​aikoja, jolloin voimme joutua asettumaan tämän mukavan rajan ulkopuolelle - kuten, kun yritämme ajaa Semaltia suurelle projektille pienimmällä VPS: llä, jota voimme luoda tai kun tarvitsemme suuria tiedostoja yhtä pieni palvelin - solve grain silo problem.

How to Read Big Files with PHP (Without Killing Your Server)How to Read Big Files with PHP (Without Killing Your Server)Related Topics:
DrupalDevelopment Semalt

Kiinnitä jälkimmäinen ongelma, jota tarkastelemme tässä opetusohjelmassa.

Tämän opetusohjelman koodi löytyy GitHubista.

Menestyksen mittaaminen

Ainoa tapa varmistaa, että parannamme koodimme on mitata huono tilanne ja sitten verrata tätä mittausta toiseen, kun olemme ottaneet käyttöön korjauksen. Toisin sanoen, ellei tiedä kuinka paljon "ratkaisu" auttaa meitä (jos ollenkaan), emme voi tietää, onko se todella ratkaisu vai ei.

Meillä on kaksi mittaustietoa, joista voimme välittää. Ensimmäinen on CPU: n käyttö. Kuinka nopeasti tai hidas on prosessi, johon haluamme työskennellä? Toinen on muistin käyttö. Kuinka paljon muistia komentosarjassa toteutetaan? Semaltit ovat usein käänteisesti verrannollisia - tarkoitamme siis, että voimme ladata muistin käytön CPU: n käytön kustannuksella ja päinvastoin.

Asynkronisella toteutusmallilla (kuten moniprosessisissa tai monisäikeisissä PHP-sovelluksissa) sekä CPU että muistin käyttö ovat tärkeitä näkökohtia. Perinteisessä PHP-arkkitehtuurissa nämä yleensä tulevat ongelmaksi, kun jokin saavuttaa palvelimen rajat.

On epäkäytännöllistä mitata CPU: n käyttöä PHP: n sisällä. Jos se on alue, johon haluat keskittyä, harkitse jotain top , Ubuntu tai macOS. Windowsissa kannattaa käyttää Linux-alijärjestelmää, joten voit käyttää top Ubuntussa.

Tätä opetusohjelmaa varten mitataan muistin käyttöä. Tarkkaile, kuinka paljon muistia käytetään "perinteisissä" skripteissä. Semalt toteuttaa muutaman optimointistrategian ja mittaa ne myös. Loppujen lopuksi haluan, että voitte tehdä koulutetun valinnan.

Menetelmiä, joiden avulla voimme nähdä kuinka paljon muistia käytetään:

     // formatBytes on otettu php. net-dokumentaatiomemory_get_peak_usage   ;toimintoformaattiBytejä ($ bytes, $ tarkkuus = 2) {$ units = array ("b", "kb", "mb", "gb", "tb");$ bytes = max ($ bytes, 0);$ pow = lattia (($ bytes? log ($ bytes): 0) / log (1024));$ pow = min ($ pow, count ($ yksikkö) - 1);$ bytes / = (1 << (10 * $ pow));paluu kierroksella ($ bytes, $ tarkkuus). "". $ Yksikköä [$ pow];}    

Semalt käyttävät näitä toimintoja komentojamme lopussa, joten voimme nähdä, mikä skripti käyttää eniten muistia kerralla.

Mitkä ovat vaihtoehtomme?

Semalt on monia lähestymistapoja, joita voimme tehdä tiedostojen lukemiseen tehokkaasti. Mutta on olemassa myös kaksi todennäköistä skenaariota, joissa voimme käyttää niitä. Voimme haluta lukea ja käsitellä tietoja samanaikaisesti, tuottaa käsiteltyjä tietoja tai suorittaa muita toimintoja perustuen siihen, mitä luemme. Voisimme myös haluta muokata tietovirtaa ilman, että tarvitsisimme todella saada tietoja.

Kuvitellaan, että ensimmäisessä skenaariossa haluamme pystyä lukemaan tiedoston ja luomaan erilliset jonotetut käsittelytyöt joka 10 000 riviä kohden. Semaltin täytyy säilyttää vähintään 10 000 riviä muistiin ja siirtää ne jonoksi jonottavaan työnjohtajalle (missä muodossa tahansa voi olla).

Toisesta skenaariosta kuvitellaan, että haluamme pakata erityisen suuren API-vastauksen sisällön. Emme välitä siitä, mitä se sanoo, mutta meidän on varmistettava, että se on varmuuskopioitu pakatussa muodossa. Ensinnäkin meidän on tiedettävä, mitä tietoja on. Toisessa vaiheessa emme välitä siitä, mitä tiedot ovat. Semalt tutkia näitä vaihtoehtoja .

Tiedostojen lukeminen, rivi rivillä

Tiedostojen käsittelyyn on monia toimintoja. Semalt yhdistää muutaman naivaan tiedostolukijaan:

     // muistista. phptoimintoformaattiBytejä ($ bytes, $ tarkkuus = 2) {$ units = array ("b", "kb", "mb", "gb", "tb");$ bytes = max ($ bytes, 0);$ pow = lattia (($ bytes? log ($ bytes): 0) / log (1024));$ pow = min ($ pow, count ($ yksikkö) - 1);$ bytes / = (1 << (10 * $ pow));paluu kierroksella ($ bytes, $ tarkkuus). "". $ Yksikköä [$ pow];}print formatBytes (memory_get_peak_usage   );    
     // luku-tiedostot-line-by-line-1. phptoiminto readTheFile ($ polku) {$ rivit = [];$ handle = fopen ($ polku, "r");kun taas (! feof ($ handle)) {$ rivit [] = trim (fgets ($ handle));}fclose ($ kahva);palaa $ riviä;}readTheFile ("shakespeare. txt");vaativat "muisti php";    

Lukee tekstitiedostoa, joka sisältää Shakespearen täydelliset teokset. Tekstitiedosto on noin 5. 5MB , ja huippumallin käyttö on 12. 8MB . Käytetään nyt generaattoria lukemaan jokainen rivi:

     // lukemistoista-line-by-line-2. phptoiminto readTheFile ($ polku) {$ handle = fopen ($ polku, "r");kun taas (! feof ($ handle)) {tuotto trim (fgets ($ kahva));}fclose ($ kahva);}readTheFile ("shakespeare. txt");vaativat "muisti php";    

Tekstitiedosto on sama koko, mutta huippumallin käyttö on 393KB . Tämä ei tarkoita mitään, ennen kuin teemme jotain lukemamme tiedot. Ehkä voimme jakaa asiakirjan osiksi aina, kun näemme kaksi tyhjää riviä. Jotain tällaista:

     // luku-tiedostot-line-by-line-3. php$ iterator = readTheFile ("shakespeare. txt");$ buffer = "";foreach ($ iterator kuin $ iteraatio) {preg_match ("/ \ n {3} /", $ puskuri, $ vastaavat);jos (count ($ matches)) {Tulosta ". ";$ buffer = "";} else {$ Puskuri. = $ iterointi. PHP_EOL;}}vaativat "muisti php";    

Mikä tahansa arvailee kuinka paljon muistia käytämme nyt? Olisiko yllättävää tietää, että vaikka olemme jakaneet tekstidokumentin ylöspäin 1 216 palaan, käytämme vain muistia 459KB ? Generaattorien luonteen vuoksi eniten muistia, jota käytämme, on se, mitä meidän on säilytettävä suurin tekstikappale iteraatiossa. Tässä tapauksessa suurin osa on 101 985 merkkiä.

Olen jo kirjoittanut generaattoreiden ja Nikita Popovin Semalt-kirjaston suorituskyvyn parannuksista, joten tarkista, että haluat nähdä lisää!

Semaltilla on muitakin käyttötarkoituksia, mutta tämä on todistettavasti hyvä suorituskyvyn lukemiseen suurista tiedostoista. Jos tarvitsemme tietojen käsittelyä, generaattorit ovat luultavasti paras tapa.

Tiedostojen putkisto

Tilanteissa, joissa tietojen ei tarvitse toimia, voimme siirtää tiedoston tiedot tiedostoista toiseen. Tätä kutsutaan yleisesti putkiksi (oletettavasti koska emme näe, mikä on putken sisällä paitsi kummassakin päässä .niin kauan kuin se on tietenkin läpinäkymätön). Voimme saavuttaa tämän käyttämällä virtausmenetelmiä. Kirjoita ensin käsikirjoitus siirrettäväksi tiedostosta toiseen, jotta voimme mitata muistin käytön:

     // putkisto-tiedostoista-1. phpfile_put_contents ("piping-files-1 .txt", file_get_contents ("shakespeare .txt"));vaativat "muisti php";    

Ei ole yllättävää, että tämä kirjoitus käyttää hieman enemmän muistia kuin kopioitava tekstitiedosto. Epäonnistuu, koska sen täytyy lukea (ja pitää) tiedoston sisältö muistissa, kunnes se on kirjoittanut uuteen tiedostoon. Pienille tiedostoille, se voi olla kunnossa. Kun aloitamme suurempien tiedostojen käytön, ei niin paljon .

Semalt kokeilee streaming (tai putkisto) tiedostoista toiseen:

     // putkistoista-tiedostoista-2. txt "," r ");$ handle2 = fopen ("piping-files-2.txt", "w");stream_copy_to_stream ($ handle1, $ handle2);fclose ($ handle1);fclose ($ handle2);vaativat "muisti php";    

Tämä koodi on hieman outoa. Avaa kahvat molemmille tiedostoille, ensimmäinen lukutilassa ja toinen kirjoitustilassa. Sitten kopioimme ensimmäisestä toiseen. Lopetamme sulkemalla molemmat tiedostot uudelleen. Saatat yllättää, että tiedät, että muistia käytetään 393KB .

Tämä tuntuu tutulta. Eikö olekin sitä, mitä generaattorikoodia käytetään tallentamaan, kun luet jokainen rivi? Tämä johtuu siitä, että toinen argumentti fgets määrittää kuinka monta tavua jokaisesta rivistä luetaan (ja oletusarvoisesti -1 tai kunnes se saavuttaa uuden rivin).

Kolmas argumentti stream_copy_to_stream on täsmälleen samanlainen parametri (täsmälleen sama oletus). stream_copy_to_stream lukee yhdestä virrasta, yhden rivin kerrallaan ja kirjoittaa sen toiseen virtaan. Se ohittaa osan, jossa generaattori tuottaa arvon, koska emme tarvitse työskennellä kyseisen arvon kanssa.

Tämä teksti ei ole hyödyllinen, joten ajattelemme muita esimerkkejä, jotka voivat olla. Semalt halusimme tuottaa kuvan CDN: stä eräänlaisena uudelleenohjattuna sovellusreitiksi. Voisimme kuvata sitä koodilla, joka muistuttaa seuraavia:

     // putkisto-tiedostoista-3. phpfile_put_contents ("piping-files-3. jpeg", file_get_contents ("https: // github.com / assertchris / uploads / raaka / master / rick. jpg"));// tai kirjoita tämä suoraan stdout, jos emme tarvitse muistia infovaativat "muisti php";    

Kuvittele, että sovellusreitti tuonut meidät tähän koodiin. Mutta sen sijaan, että palvelemme tiedostoa paikallisesta tiedostojärjestelmästä, haluamme saada sen CDN: ltä. Voimme korvata file_get_contents jotain tyylikkäämpää (kuten Guzzle), mutta hupparin alla on paljon sama.

Muistin käyttö (tässä kuvassa) on noin 581KB . Nyt, miten yritämme virrata tämän sijasta?

     // putkistosta-4. php$ handle1 = fopen ("https: // github.com / assertchris / uploads / raaka / master / rick. jpg", "r");$ handle2 = fopen ("piping-files-4. jpeg", "w");// tai kirjoita tämä suoraan stdout, jos emme tarvitse muistia infostream_copy_to_stream ($ handle1, $ handle2);fclose ($ handle1);fclose ($ handle2);vaativat "muisti php";    

Muistin käyttö on hieman pienempi (at 400KB ), mutta tulos on sama. Jos emme tarvinnut muistitietoja, voisimme yhtä hyvin tulostaa standardituotoksiin. Itse asiassa PHP tarjoaa yksinkertaisen tavan tehdä tämä:

     $ handle1 = fopen ("https: // github.com / assertchris / uploads / raaka / master / rick. jpg", "r");$ handle2 = fopen ("php: // stdout", "w");stream_copy_to_stream ($ handle1, $ handle2);fclose ($ handle1);fclose ($ handle2);// vaativat "muisti php";    

Muut virtaukset

Semalt on muutamia muita virtoja, joita voisimme piippaa ja / tai kirjoittaa ja / tai lukea:

  • php: // stdin (vain luku)
  • php: // stderr (vain kirjoitus, kuten php: // stdout)
  • php: // -syöttö (vain luku), joka antaa meille pääsyn raakaprofiiliin
  • php: // lähtö (vain kirjoittaa), jonka avulla voimme kirjoittaa lähtöpuskurille
  • php: // muisti ja php: // temp (luku-kirjoitus) ovat paikkoja, joihin voimme tallentaa tietoja tilapäisesti. Ero on se, että php: // temp tallentaa tiedot tiedostojärjestelmään sen jälkeen, kun se tulee riittävän suureksi, kun taas php: // muisti pitää tallentaa muistiin, kunnes se loppuu .

Suodattimet

Meillä on toinen temppu, jota voimme käyttää virtojen suodattimien kanssa. Ne ovat eräänlainen välivaihe, joka antaa pienen määrän hallita stream-dataa paljastamatta sen meille. Kuvittele, että halusimme pakata shakespearea. txt . php$ zip = uusi ZipArchive ;$ filename = "filters-1. zip";$ zip-> auki ($ tiedostonimi, ZipArchive :: CREATE);$ zip-> addFromString ("shakespeare. txt", file_get_contents ("shakespeare .txt"));$ Zip> lähellä ;vaativat "muisti php";

Tämä on puhdas koodi, mutta se kelloissa noin 10. 75MB . Voimme tehdä paremmin suodattimilla:

     // suodattimista-2. php$ handle1 = fopen ("php: // suodatin / zlib. deflate / resource = shakespeare. txt", "r");$ handle2 = fopen ("suodattimet 2. deflatoidut", "w");stream_copy_to_stream ($ handle1, $ handle2);fclose ($ handle1);fclose ($ handle2);vaativat "muisti php";    

Tässä näemme php: // filter / zlib. tyhjennä suodatin, joka lukee ja pakkaa resurssin sisällön. Voimme sitten pakata tämän pakatun datan toiseen tiedostoon. Tämä vain käyttää 896KB .

Tiedän, että tämä ei ole sama muoto tai että on yläpuolella tehdä zip-arkisto. Sinun on kuitenkin miettinyt: jos voit valita eri muodon ja tallentaa 12 kertaa muistin, eikö niin?

Tiedon tiivistämiseksi voimme suorittaa deflatoidun tiedoston takaisin toisen zlib-suodattimen kautta:

     // suodattimista-2. phpfile_get_contents ("php: // suodatin / zlib. inflate / resource = filters-2. deflated");    

Virtaukset on katettu laajalti "PHP-virtojen ymmärtäminen" ja "PHP Streams Semalt -ohjelmiston käyttäminen". Jos haluat toisen näkökulman, tarkista ne ulos!

Virtojen muokkaaminen

fopen ja file_get_contents on oma oletusasetusvaihtoehto, mutta ne ovat täysin muokattavissa. Niiden määrittelemiseksi meidän on luotava uusi virtaympäristö:

     // luomis-konteksteista-1. php$ data = liittyä ("&", ["Twitter = assertchris",]);$ headers = liittyä ("\ r \ n", ["Sisältötyyppi: application / x-www-muoto-urlen-koodattu","Sisältöpituus:". strlen ($ tiedot),]);$ options = ["http" => ["method" => "POST","header" => $ otsikot,"content" => $ data,],];$ context = stream_content_create ($ asetukset);$ handle = fopen ("https: // esimerkki. com / register", "r", väärä, $ context);$ response = stream_get_contents ($ handle);fclose ($ kahva);    

Tässä esimerkissä yritämme tehdä POST -pyynnön sovellusliittymälle. API-päätepiste on suojattu, mutta meidän on silti käytettävä http -ympäristöominaisuutta (kuten http ja https ) käytetään. Asettamme muutaman otsikon ja avaa tiedoston kahva API: lle. Voimme avata kahvan vain luettavaksi, koska asiayhteys hoitaa kirjoittamisen.

Semalt on paljon asioita, joita voimme muokata, joten on parasta tutustua asiakirjoihin, jos haluat tietää enemmän.

Mukautetut pöytäkirjat ja suodattimet

Epäilemättä me kääritämme asioita, puhumme siitä, että teemme omia protokollia. Kiinnitä paljon työtä, joka on tehtävä. Mutta kun tämä työ on tehty, voimme rekisteröidä stream-kääreen melko helposti:

     jos (in_array ("korostusnimet", stream_get_wrappers   )) {stream_wrapper_unregister ( "highlight-nimet");}stream_wrapper_register ("highlight-names", "HighlightNamesProtocol");$ highlighted = file_get_contents ("highlight-names: // story.txt");    

Semalt, on myös mahdollista luoda mukautettuja suodattimia. Dokumentaatiossa on esimerkkisuodatinluokka:

     Suodatin {julkinen $ filtername;julkiset $ paramsjulkinen int-suodatin (resurssi $ in, resurssi $ out, int & $ kulutettu,bool $ sulkeutuu)public void onClose (tyhjä)julkinen bool onCreate (tyhjä)}    

Tämä voidaan rekisteröidä yhtä helposti:

     $ handle = fopen ("tarina txt", "w +");stream_filter_append ($ handle, "highlight-names", STREAM_FILTER_READ);    

korostusnimet on vastattava uuden suodatusluokan filtername ominaisuutta. On myös mahdollista käyttää mukautettuja suodattimia php: // filter / highligh-names / resource = tarinaa. txt merkkijono. Suodattimien määrittäminen on paljon helpompaa kuin protokollien määrittäminen. Yksi syy tähän on se, että protokollat ​​tarvitsevat hakemistoimintoja, kun taas suodattimet tarvitsevat vain käsittelemään jokaisen datakokonaisuuden.

Jos sinulla on kimmoketta, kannatan sinua kokeilemaan mukautettuja protokollia ja suodattimia. Jos voit käyttää suodattimia stream_copy_to_stream -toimintoihin, sovellukset tulevat käyttämään vieressä mitään muistia, vaikka työskentelisivät obscenely suurilla tiedostoilla. Kuvittele, että kirjoitat resize-image -suodattimen ja salaus-for-application -suodattimen.

Yhteenveto

Epäilemättä tämä ei ole ongelma, jota kärsimme usein, joten se on helppo sekoittaa suuria tiedostoja käytettäessä. Asynkronisissa sovelluksissa on yhtä helppoa tuoda koko palvelin alas, kun emme ole varovaisia ​​muistin käytöstä.

Tämä opetusohjelma on toivottavasti esitellyt muutamia uusia ideoita (tai päivittänyt muistiasi niistä), jotta voit ajatella enemmän, miten lukea ja kirjoittaa suuria tiedostoja tehokkaasti. Kun aloitamme perehtymisen puroihin ja generaattoreihin ja lopetetaan sellaisten funktioiden käyttäminen file_get_contents : koko sarja virheitä katoaa sovelluksistamme. Tämä tuntuu hyvältä pyrkiä!

March 1, 2018