Back to Question Center
0

Bagaimana Membaca Fail Big dengan PHP (Tanpa Membunuh Pelayan Anda) Bagaimana Membaca Fail Big dengan PHP (Tanpa Membunuh Pelayan Anda) Topik Berkaitan: Drupal Development Semalt

1 answers:
Bagaimana Membaca Fail Big dengan PHP (Tanpa Membunuh Pelayan Anda)

Tidak semestinya kita, sebagai pemaju PHP, perlu bimbang tentang pengurusan memori. Enjin PHP melakukan kerja pembersihan selepas kami, dan model pelayan web bagi konteks pelaksanaan jangka pendek bermaksud walaupun kod sloppiest tidak mempunyai kesan jangka panjang.

Terdapat masa yang jarang berlaku apabila kita perlu melangkah keluar dari sempadan yang selesa ini - seperti ketika kita cuba untuk menjalankan Semalt untuk projek besar pada VPS terkecil yang kita boleh buat, atau ketika kita perlu membaca fail besar pada pelayan yang sama kecil - no deposit online casinos codes.

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

Semalat masalah terakhir yang akan kita lihat dalam tutorial ini.

Kod untuk tutorial ini boleh didapati di GitHub.

Mengukur Kejayaan

Satu-satunya cara untuk memastikan kami membuat apa-apa penambahbaikan pada kod kami adalah untuk mengukur keadaan yang tidak baik dan kemudian membandingkan ukuran itu kepada yang lain selepas kami menggunakan penetapan kami. Dalam erti kata lain, melainkan kita tahu berapa banyak "penyelesaian" yang membantu kita (jika sama sekali), kita tidak dapat mengetahui sama ada ia benar-benar penyelesaian atau tidak.

Terdapat dua metrik yang boleh kita ambil perhatian. Yang pertama ialah penggunaan CPU. Berapa cepat atau lambat adalah proses yang kami mahu bekerjasama? Yang kedua adalah penggunaan ingatan. Berapa banyak memori yang diperlukan oleh skrip untuk dilaksanakan? Semalt sering berkadar songsang - bermakna bahawa kita boleh memunggah penggunaan memori pada kos penggunaan CPU, dan sebaliknya.

Dalam model pelaksanaan asynchronous (seperti dengan aplikasi PHP berbilang proses atau multi-threaded), penggunaan CPU dan memori adalah pertimbangan yang penting. Dalam seni bina PHP tradisional, ini secara amnya menjadi masalah apabila seseorang mencapai batas pelayan.

Tidak praktikal untuk mengukur penggunaan CPU di dalam PHP. Jika itu kawasan yang ingin anda tumpukan, pertimbangkan untuk menggunakan sesuatu seperti atas , pada Ubuntu atau macOS. Untuk Windows, pertimbangkan untuk menggunakan Subsystem Linux, jadi anda boleh menggunakan atas di Ubuntu.

Untuk tujuan tutorial ini, kita akan mengukur penggunaan memori. Semir melihat berapa banyak memori digunakan dalam skrip "tradisional". Semalt melaksanakan beberapa strategi pengoptimuman dan mengukur mereka juga. Pada akhirnya, saya mahu anda dapat membuat pilihan berpendidikan.

Kaedah yang akan kita gunakan untuk melihat berapa banyak memori yang digunakan ialah:

     // formatBytes diambil dari php. dokumentasi bersihmemory_get_peak_usage   ;format fungsiBytes ($ bait, $ precision = 2) {$ units = array ("b", "kb", "mb", "gb", "tb");$ bytes = max ($ bait, 0);$ pow = lantai (($ bait? log ($ bait): 0) / log (1024));$ pow = min ($ pow, count ($ units) - 1);$ bytes / = (1 << (10 * $ pow));kembali bulat ($ bait, ketepatan $). "". $ units [$ pow];}    

Semalt menggunakan fungsi ini pada akhir skrip kami, sehingga kita dapat melihat skrip mana yang menggunakan memori paling banyak pada satu masa.

Apa Pilihan Kami?

Semalt adalah banyak pendekatan yang boleh kita ambil untuk membaca fail dengan cekap. Tetapi terdapat juga dua kemungkinan senario di mana kita boleh menggunakannya. Kita boleh membaca dan memproses data semua pada masa yang sama, mengeluarkan data yang diproses atau melakukan tindakan lain berdasarkan apa yang kita baca. Kami juga mahu mengubah aliran data tanpa memerlukan akses kepada data.

Bayangkan, untuk senario pertama, kita mahu dapat membaca fail dan membuat kerja pemprosesan beratur berasingan setiap 10,000 baris. Semalt perlu menyimpan sekurang-kurangnya 10,000 baris ingatan, dan lulus mereka ke pengurus tugas yang beratur (apa bentuk yang mungkin diambil).

Untuk senario kedua, bayangkan kita mahu memampatkan kandungan respon API yang sangat besar. Kami tidak peduli apa yang dikatakannya, tetapi kami perlu memastikan ia disokong dalam bentuk termampat. Pada mulanya, kita perlu tahu apa data itu. Pada yang kedua, kita tidak peduli apa data itu. Semalt meneroka pilihan ini .

Membaca Fail, Baris Jalur

Terdapat banyak fungsi untuk bekerja dengan fail. Semalt menggabungkan beberapa ke dalam pembaca fail naif:

     // dari ingatan. phpformat fungsiBytes ($ bait, $ precision = 2) {$ units = array ("b", "kb", "mb", "gb", "tb");$ bytes = max ($ bait, 0);$ pow = lantai (($ bait? log ($ bait): 0) / log (1024));$ pow = min ($ pow, count ($ units) - 1);$ bytes / = (1 << (10 * $ pow));kembali bulat ($ bait, ketepatan $). "". $ units [$ pow];}format cetakanBytes (memory_get_peak_usage   );    
     // dari membaca-fail-line-by-line-1. phpfungsi readTheFile ($ path) {$ lines = [];$ handle = fopen ($ path, "r");sementara (! feof ($ handle)) {$ baris [] = trim (fgets ($ pegangan));}fclose ($ handle);baris $ kembali;}readTheFile ("shakespeare. txt");memerlukan "memori php";    

Kami membaca fail teks yang mengandungi karya lengkap Shakespeare. Fail teks adalah kira-kira 5. 5MB , dan penggunaan memori puncak adalah 12. 8MB . Sekarang, mari kita gunakan penjana untuk membaca setiap baris:

     // dari bacaan-files-line-by-line-2. phpfungsi readTheFile ($ path) {$ handle = fopen ($ path, "r");sementara (! feof ($ handle)) {trim hasil (fgets (pemegang $));}fclose ($ handle);}readTheFile ("shakespeare. txt");memerlukan "memori php";    

Fail teks adalah saiz yang sama, tetapi penggunaan memori puncak ialah 393KB . Ini tidak bermakna apa-apa sehingga kami melakukan sesuatu dengan data yang kami baca. Mungkin kita boleh memisahkan dokumen itu ke dalam ketulan setiap kali kita melihat dua baris kosong. Sesuatu seperti ini:

     // dari bacaan-fail-line-by-line-3. php$ iterator = readTheFile ("shakespeare. txt");$ buffer = "";foreach ($ iterator as $ iteration) {preg_match ("/ \ n {3} /", penampan $, padanan $);jika (count ($ matches)) {cetak ".";$ buffer = "";} else {penampan $. = $ lelaran. PHP_EOL;}}memerlukan "memori php";    

Apa-apa meneka berapa banyak memori yang kita gunakan sekarang? Adakah anda mengejutkan anda untuk mengetahui bahawa, walaupun kami memecah dokumen teks ke dalam 1,216 ketulan, kita masih hanya menggunakan 459KB ingatan? Memandangkan sifat penjana, ingatan yang paling akan kita gunakan ialah yang kita perlu menyimpan sebahagian teks terbesar dalam lelaran. Dalam kes ini, bahagian terbesar ialah 101,985 aksara.

Saya sudah menulis mengenai peningkatan prestasi menggunakan penjana dan perpustakaan Semalt Nikita Popov, jadi pastikan bahawa jika anda ingin melihat lebih banyak lagi!

Semalt mempunyai kegunaan lain, tetapi yang satu ini sangat baik untuk membaca pemain fail besar. Jika kita perlu bekerja pada data, penjana mungkin cara terbaik.

Piping Antara Fail

Dalam keadaan di mana kita tidak perlu mengendalikan data, kita boleh lulus data fail dari satu fail ke fail lain. Ini biasanya dipanggil piping (mungkin kerana kita tidak melihat apa yang ada di dalam paip kecuali di setiap hujung .selagi ia legap, tentu saja!). Kita boleh mencapai ini dengan menggunakan kaedah aliran. Pertama kita tulis skrip untuk dipindahkan dari satu fail ke fail yang lain, supaya kita dapat mengukur penggunaan memori:

     // dari piping-files-1. phpfile_put_contents ("piping-files-1 txt", file_get_contents ("shakespeare. txt"));memerlukan "memori php";    

Tidak mengejutkan, skrip ini menggunakan sedikit memori untuk dijalankan daripada fail teks yang ia salinan. Semalt kerana ia harus membaca (dan menyimpan) kandungan fail dalam memori sehingga ia telah ditulis ke fail baru. Untuk fail kecil, itu mungkin baik-baik saja. Apabila kita mula menggunakan fail yang lebih besar, tidak banyak .

Semal cuba aliran (atau pipa) dari satu fail ke yang lain:

     // dari piping-files-2. txt "," r ");$ handle2 = fopen ("paip-fail-2 txt", "w");stream_copy_to_stream ($ handle1, $ handle2);fclose ($ handle1);fclose ($ handle2);memerlukan "memori php";    

Kod ini sedikit aneh. Kami membuka pemegang ke kedua-dua fail, yang pertama dalam mod bacaan dan yang kedua dalam mod tulis. Kemudian kita salin dari yang pertama ke yang kedua. Kami selesai dengan menutup kedua-dua fail sekali lagi. Ia mungkin mengejutkan anda untuk mengetahui bahawa memori yang digunakan adalah 393KB .

Itu kelihatan biasa. Bukankah kod kod penjana yang digunakan untuk menyimpan ketika membaca setiap baris? Ini kerana hujah kedua untuk fgets menentukan berapa bait setiap baris untuk membaca (dan lalai ke -1 atau sampai ia mencapai garis baru).

Hujah ketiga ke stream_copy_to_stream adalah jenis parameter yang sama (sama dengan piawai yang sama). stream_copy_to_stream membaca dari satu strim, satu baris pada satu masa, dan menuliskannya ke strim yang lain. Ia melompat bahagian di mana penjana menghasilkan nilai, kerana kita tidak perlu bekerja dengan nilai itu.

Paip teks ini tidak berguna kepada kami, jadi mari kita fikirkan contoh lain yang mungkin. Semalt kami mahu mengeluarkan imej dari CDN kami, sebagai satu laluan laluan yang diarahkan semula. Kita boleh menggambarkannya dengan kod yang menyerupai berikut:

     // dari piping-files-3. phpfile_put_contents ("piping-files-3 jpeg", file_get_contents ("https: // github com / assertchris / upload / mentah / master / rick jpg"));// atau tulis lurus ke stdout ini, jika kita tidak memerlukan info memorimemerlukan "memori php";    

Bayangkan laluan aplikasi yang membawa kita kepada kod ini. Tetapi bukannya menyampaikan fail dari sistem fail tempatan, kami ingin mendapatkannya dari CDN. Kami boleh menggantikan file_get_contents untuk sesuatu yang lebih elegan (seperti Guzzle), tetapi di bawah hud itu sama.

Penggunaan memori (untuk imej ini) adalah sekitar 581KB . Sekarang, bagaimana pula kita cuba mengalir ini?

     // dari piping-files-4. php$ handle1 = fopen ("https: // github com / assertchris / upload / mentah / master / rick jpg", "r");$ handle2 = fopen ("piping-files-4 jpeg", "w");// atau tulis lurus ke stdout ini, jika kita tidak memerlukan info memoristream_copy_to_stream ($ handle1, $ handle2);fclose ($ handle1);fclose ($ handle2);memerlukan "memori php";    

Penggunaan memori sedikit kurang (di 400KB ), tetapi hasilnya adalah sama. Sekiranya kita tidak memerlukan maklumat memori, kita boleh mencetak output standard. Malah, PHP menyediakan cara mudah untuk melakukan ini:

     $ handle1 = fopen ("https: // github com / assertchris / upload / mentah / master / rick jpg", "r");$ handle2 = fopen ("php: // stdout", "w");stream_copy_to_stream ($ handle1, $ handle2);fclose ($ handle1);fclose ($ handle2);// memerlukan "memori php";    

Aliran lain

Semalt adalah beberapa aliran lain yang kami boleh paip dan / atau menulis kepada dan / atau dibaca dari:

  • php: // stdin (baca sahaja)
  • php: // stderr (hanya menulis, seperti php: // stdout)
  • php: // input (read-only) yang memberikan kita akses kepada badan permintaan mentah
  • php: // output (menulis sahaja) yang membolehkan kita menulis kepada penampan output
  • php: // memory dan php: // temp (read-write) adalah tempat kita boleh menyimpan data buat sementara waktu. Perbezaannya ialah php: // temp akan menyimpan data dalam sistem fail sebaik sahaja ia menjadi cukup besar, sementara php: // memory akan menyimpan dalam memori sehingga kehabisan .

Penapis

Terdapat helah lain yang boleh kita gunakan dengan aliran yang disebut penapis . Mereka adalah sejenis langkah antara, memberikan sedikit kawalan ke atas data aliran tanpa mendedahkannya kepada kami. Bayangkan kami mahu memampatkan shakespeare kami. txt . php$ zip = new ZipArchive ;$ filename = "filters-1. zip";$ zip-> buka ($ filename, ZipArchive :: CREATE);$ zip-> addFromString ("shakespeare. txt", file_get_contents ("shakespeare. txt"));$ zip-> tutup ;memerlukan "memori php";

Ini adalah sedikit kod yang kemas, tetapi pada jam sekitar 10. 75MB . Kita boleh berbuat lebih baik, dengan penapis:

     // dari penapis-2. php$ handle1 = fopen ("php: // filter / zlib. deflate / resource = shakespeare. txt", "r");$ handle2 = fopen ("penapis-2. deflated", "w");stream_copy_to_stream ($ handle1, $ handle2);fclose ($ handle1);fclose ($ handle2);memerlukan "memori php";    

Di sini, kita dapat melihat php: // filter / zlib. deflate penapis, yang membaca dan memampatkan kandungan sumber. Kami kemudian boleh paip data mampat ini ke fail lain. Ini hanya menggunakan 896KB .

Saya tahu ini bukan format yang sama, atau bahawa terdapat gangguan untuk membuat arkib zip. Anda perlu tertanya-tanya: jika anda boleh memilih format yang berbeza dan menyimpan 12 kali memori, bukan?

Untuk menyahsapkan data, kita dapat menjalankan fail yang digulingkan melalui penapis zlib yang lain:

     // dari penapis-2. phpfile_get_contents ("php: // filter / zlib inflate / resource = filters-2. deflated");    

Aliran telah diliputi secara meluas dalam "Memahami Aliran dalam PHP" dan "Menggunakan PHP Stream Semalt". Jika anda ingin perspektif yang berbeza, periksa mereka!

Menyesuaikan aliran

fopen dan file_get_contents mempunyai set pilihan lalai mereka sendiri, tetapi ini boleh disesuaikan sepenuhnya. Untuk mentakrifkannya, kita perlu membuat konteks aliran baru:

     // dari penciptaan-konteks-1. php$ data = join ("&", ["twitter = assertchris",]);$ headers = join ("\ r \ n", ["Jenis kandungan: aplikasi / x-www-borang-urlencoded","Panjang kandungan:". strlen ($ data),]);$ options = ["http" => ["kaedah" => "POST","header" => $ header,"kandungan" => $ data,],];$ context = stream_content_create ($ options);$ handle = fopen ("https: // example com / register", "r", false, $ context);sambutan $ = stream_get_contents ($ pegangan);fclose ($ handle);    

Dalam contoh ini, kami cuba membuat permintaan POST ke API. Titik akhir API selamat, tetapi kami masih perlu menggunakan laman konteks http (seperti yang digunakan untuk http dan https ). Kami menetapkan beberapa tajuk dan membuka pemegang fail kepada API. Kita boleh membuka pemegang sebagai bacaan sahaja kerana konteksnya menjaga penulisan.

Semalt adalah banyak perkara yang boleh kami sediakan, jadi sebaiknya periksa dokumentasi jika anda ingin mengetahui lebih lanjut.

Membuat Protokol dan Penapis Tersuai

Semalat kita membungkus perkara, mari kita bercakap tentang membuat protokol adat. Semalam banyak kerja yang perlu dilakukan. Tetapi sebaik sahaja kerja selesai, kami boleh mendaftarkan pembalut strim kami dengan mudah:

     jika (dalam_array ("highlight-names", stream_get_wrappers   )) {stream_wrapper_unregister ("highlight-names");}stream_wrapper_register ("highlight-names", "HighlightNamesProtocol");$ disorot = file_get_contents ("highlight-names: // story txt");    

Semalat, ia juga mungkin untuk membuat penapis aliran tersuai. Dokumentasi mempunyai kelas penapis contoh:

     Tapisan {public $ filtername;param $ awampenapis int awam (sumber $ dalam, sumber $ keluar, int & $ yang digunakan,bool $ penutup)kekosongan awam diClose (tidak sah)bool awam onCreate (tidak sah)}    

Ini boleh didaftarkan dengan mudah seperti:

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

nama sorot perlu dipadankan dengan filtername harta kelas penapis baru. Ia juga mungkin menggunakan penapis tersuai dalam php: // filter / highligh-names / resource = story. txt rentetan. Ia lebih mudah untuk menentukan penapis daripada untuk menentukan protokol. Satu sebab untuk ini ialah protokol perlu mengendalikan operasi direktori, sedangkan penapis hanya perlu mengendalikan setiap data.

Jika anda mempunyai kegunaan, saya amat menggalakkan anda untuk mencuba dengan membuat protokol dan penapis tersuai. Jika anda boleh memohon penapis ke operasi stream_copy_to_stream , aplikasi anda akan digunakan bersebelahan dengan memori tanpa walaupun berfungsi dengan fail yang besar. Bayangkan menulis penapis resize-image atau dan penapis enkripsi untuk aplikasi .

Ringkasan

Semalat ini bukanlah masalah yang sering kita hadapi, mudah merosakkan apabila bekerja dengan fail besar. Dalam aplikasi tak segerak, ia semudah membawa seluruh pelayan turun apabila kami tidak berhati-hati tentang penggunaan memori.

Tutorial ini sememangnya telah memperkenalkan anda kepada beberapa idea baru (atau menyegarkan ingatan anda tentangnya), supaya anda boleh berfikir lebih lanjut mengenai cara membaca dan menulis fail besar dengan cekap. Apabila kita mula menjadi biasa dengan aliran dan penjana, dan berhenti menggunakan fungsi seperti file_get_contents : keseluruhan kategori ralat hilang dari aplikasi kami. Itu seolah-olah satu perkara yang baik untuk tujuan!

March 1, 2018