Kuinka lukea suuria tiedostoja PHP: llä (ilman palvelimen tappamista) Kuinka lukea suuria tiedostoja PHP: llä (ilman palvelimen tappamista) Aiheeseen liittyviä artikkeleita: Drupal Development Semalt
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.
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 - create stylish fonts generator. 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
japhp: // 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 taasphp: // 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 Tämä on puhdas koodi, mutta se kelloissa noin 10. 75MB . Voimme tehdä paremmin suodattimilla: Tässä näemme 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: 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! Tässä esimerkissä yritämme tehdä Semalt on paljon asioita, joita voimme muokata, joten on parasta tutustua asiakirjoihin, jos haluat tietää enemmän. 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: Semalt, on myös mahdollista luoda mukautettuja suodattimia. Dokumentaatiossa on esimerkkisuodatinluokka: Tämä voidaan rekisteröidä yhtä helposti: Jos sinulla on kimmoketta, kannatan sinua kokeilemaan mukautettuja protokollia ja suodattimia. Jos voit käyttää suodattimia 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 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";
// 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";
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 .
// suodattimista-2. phpfile_get_contents ("php: // suodatin / zlib. inflate / resource = filters-2. deflated");
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);
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. Mukautetut pöytäkirjat ja suodattimet
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");
Suodatin {julkinen $ filtername;julkiset $ paramsjulkinen int-suodatin (resurssi $ in, resurssi $ out, int & $ kulutettu,bool $ sulkeutuu)public void onClose (tyhjä)julkinen bool onCreate (tyhjä)}
$ 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. 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
file_get_contents
: koko sarja virheitä katoaa sovelluksistamme. Tämä tuntuu hyvältä pyrkiä!