Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [PHP] Wyciek pamięci
Forum PHP.pl > Forum > Przedszkole
SmokAnalog
Witajcie,

nie do końca wiem jak w PHP narazić się na wyciek pamięci, ale chyba właśnie padłem jego ofiarą. Mam skrypt konsolowy, który czyta po kolei strony z zewnętrznego serwera i zawsze po około 40-50-ciu tysiącach iteracji otrzymuję błąd w stylu:

Cytat
PHP Fatal error: Allowed memory size of 536870912 bytes exhausted (tried to allocate 421888 bytes) in ... on line 36


A to mój kod (uprościłem dla przykładu):

  1. for ($id = $initialId;; $id += 1) {
  2. echo 'Trying #' . $id . ': ';
  3.  
  4. try {
  5. $html = file_get_contents('http://domena.com/page/' . $id);
  6.  
  7. // ...
  8. } catch (Exception $exception) {
  9. echo 'FAIL';
  10. }
  11. }


Linia 36. to:
  1. $html = file_get_contents('http://domena.com/page/' . $id);


Czy po kroku iteracji zawartość $html dalej jest trzymana w pamięci? Jakoś nie chce mi się w to wierzyć.
com
PHP to język interpretowany, on przetwarza pokolei, a na końcu zwraca Ci dopiero wynik, wiec gdzieś te informację musi w pamieć przechować, dodatkowo domyślnie masz ograniczenia zużycia pamieć dla skryptu, które zawsze można próbować sobie podnieść.

zrób to jakimś systemem kolejkowym najlepiej smile.gif
SmokAnalog
Dzięki, ale to chyba nie jest odpowiedź na moje pytanie? Co ma do tego system kolejkowy? Poza tym to, że przetwarza po kolei nie znaczy, że też nie ma wewnętrznego zarządzania pamięcią?
trzczy
Czasami ograniczenia pamięci wkurzają. Np. niezajęta partycja swap, a komunikat, że brakuje pamięci. Z ciekawości: jakie jest tam ustawienie memory_limit w php.ini?
com
https://github.com/php/php-src/blob/master/...ard/file.c#L522 tutaj jest kod za to odpowiedzialny biggrin.gif
Jest zarządzie pamięcią robi malloc i gdzieś tam pewnie jest ta pamieć zwalniana ale niekoniecznie od razu.

No system kolejkowy, żeby wrzucić te operacje na kolejkę i przetworzyć a nie robić to w pętli tak jak masz teraz
darko
Wyciek pamięci, a wyczerpanie dozwolonego limitu zużycia pamięci dla skryptu to dwie różne kwestie. Zwróć uwagę na to, że nie podałeś warunku zakończenia pętli for, zatem nie dziw się, że mieli w nieskończoność aż dojdzie do limitu pamięci.
SmokAnalog
@darko tylko co ma do tego brak warunku? Ma mielić w nieskończoność i dobrze napisany kod nie powinien doprowadzić do wycieku nawet gdy mieli przez trylion lat. Twoim tokiem rozumowania to każda pierwsza lepsza gra komputerowa powinna wywoływać wyciek.
trueblue
Coś się poprawia dodając unset($html) i/lub http://php.net/manual/en/function.gc-collect-cycles.php ?
com
Ale zacznijmy od tego że PHP, nie koniecznie jest dobrze napisanym kodem, poza tym gry w PHP się nie pisze to zupełnie dwa rożne światy wink.gif
darko
Nie zrozumieliśmy się. Problemy ze zwalnianiem pamięci w pętlach w PHP są tak stare jak świat. Na podstawie tego fragmentu kodu trudno określić co dzieje się ze zmienną $html dalej i czy robisz tam jakiś unset czy nie. Użyj profilera, poświęć czas na analizę logu, poeksperymentuj z garbage collectorem. W sieci można znaleźć wiele tematów dotyczących problemów z pamięcią i używaniem takich funkcji jak json_decode, file_get_contents i funkcjami operującymi na xmlu. PHP generalnie średnio nadaje się do pisania rozwiązań działających na zasadzie demonów właśnie przez znane problemy ze zwalnianiem pamięci.
phpion
Nie jest przypadkiem tak, że dla konkretnego adresu wczytywanie jego zawartości powoduje przekoreczenie pamięci? Może jest tam tak duża zawartość ze wczytanie jej do zmiennej w PHP powoduje przekoreczenie limitu. Ustal czy błąd powstaje każdorazowo dla tego samego adresu ($id).
SmokAnalog
@phpion Jestem pewien. Jedna strona nie ma 512 MB. Rozumiem, że podważasz zdanie kolegów wyżej?

@darko Nie wiem czy PHP się aż tak różni, żeby uniemożliwiać użycie pętli idących w tysiące iteracji. Zalicza się go jednak do języków ogólnego przeznaczenia. Świat PHP nie kończy się na requestach i response'ach.

@trueblue Muszę właśnie zrobić taki test. Zmienię limit pamięci na malutki, np. 1 MB, dam unset i zobaczę. Do głowy przychodzi mi jeszcze jedna możliwość. Używam w tej pętli kilka razy preg_match z trzecim argumentem, na przykład:

  1. preg_match('#personId=(\d+)#', $html, $matches);


Może ten trzeci argument, podawany w końcu jako referencja, powoduje wyciek? Tak naprawdę nie jest powiedziane, że to właśnie zmienna $html wyciekła. To ona spowodowała przekroczenie limitu, ale być może zapchało go coś innego? Tylko że poza preg_match, zapytaniami do bazy i echo nic tam innego nie ma, a bez echo też występuje ten błąd.
phpion
Do zmiennej $html wczytujesz ponad 500 Mb danych co powoduje przekroczenie pamięci. Żadne unsety tutaj nie pomogą bo sama zmienna zawiera w sobie zbyt wiele danych.
SmokAnalog
Eee, jakim cudem wczytuję do zmiennej $html 500 mega? haha.gif Przecież nie appenduję do tego stringa, tylko na nowo ustawiam.
markuz
memory_get_usage

Po prostu sprawdź w którym momencie ilość pamięci wzrasta. Może masz jakąś tablicę której nie czyścisz, może to jakiś wbudowany mechanizm PHP dla danej funkcji. Wywołaj tą funkcję po każdym "bloku", następnie w danym "bloku" (tym który zwiększa zużycie) po każdej linijce itp. Jak znajdziesz linijkę to już pewnie sobie poradzisz.

Podawanie fragmentu kodu jest dobre w większości przypadków - ale nie tutaj, możesz pokazać całość. Pokazałeś tylko file_get_contents a na 99% przyczyna jest w innym miejscu.

To, że w tej lini występuje błąd też nic nie mówi - jeżeli mamy 50 kb dostępnej pamięci, a file_get_contents zabiera tylko 5 kb pamięci, to chcemy się dowiedzieć skąd pochodzi reszta tj. 45 kb. A, że akurat została przekroczona w tym miejscu - to bez znaczenia.
SmokAnalog
Masz rację, że to nie dowód. Sam o tym napisałem powyżej. A co myślisz o koncepcji z preg_match?
markuz
Nic nie myślę, po prostu to sprawdź tą funkcją smile.gif
SmokAnalog
Jasne. Sprawdzę jak dotrę do domu smile.gif
phpion
Mój błąd, przeczytałem ze wczytujesz więcej niż 500Mb. Tak czy inaczej piszesz ze błąd dotyczy linii z file_get_contents wiec chyba w tym miejscu przekraczasz pamięć. Użyj podanej przez markuza funkcji badając zużycie pamięci w poszczególnych liniach/iteracjach. Teoretycznie przy każdej iteracji pamięć niekoniecznie powinna wzrastać bo jest to zależne od rozmiaru obrabianych danych. Ustal tez czy problem tyczy konkretnego $id czy pojawia sie losowo.
SmokAnalog
Owszem, ale na logikę: to nie znaczy, że to ta linia powoduje wyciek. Ona przekracza, co jest zrozumiałe, bo potrzebuje jej najwięcej. Ale kumulować pamięć może cokolwiek imnego.

Dobra Panowie. Już wiem co powoduje wyciek.

Przepraszam, że dopiero teraz to mówię, ale tak obsługuję wyjątki w tym skrypcie:

  1. set_error_handler(function ($severity, $message, $file, $line) {
  2. if (!(error_reporting() & $severity)) {
  3. // This error code is not included in error_reporting
  4. return;
  5. }
  6.  
  7. throw new ErrorException($message, 0, $severity, $file, $line);
  8. });


Ilekroć strona nie istnieje (404), mój file_get_contents wyrzuca wyjątek. Tak ma być, ale jest jeden problem. Każdy poprzedni $html jest zapisywany jako stack trace tego wyjątku. Zrobiłem test polegający na tym, że wypisuję ilość zużywanej pamięci i dodatkowo ustawiłem na sztywno numerek dla URL-a na taki, który zwraca 404. Pamięć rośnie wtedy jak szalona. W moim normalnym użyciu, tych 404 nie ma aż tak dużo, ale wystarczająco, by w końcu przepełnić pamięć.

Jak mogę sprawić, by te wyjątki nie przechowywały całego stosu? Nie ukrywam, że najchętniej zostałbym przy wyjątkach zamiast klasycznych błędów. Próbowałem dać xdebug_disable();, ale nie istnieje u mnie taka funkcja.
Pyton_000
Jeśli używasz PHP7 to w teorii powinno Ci samo wywalić Exception. Próbowałeś to wywalić?
SmokAnalog
Wyrzuca stary, dobry warning na PHP 7.2.
com
Ciężko jest odtworzyć Twój błąd nawet z zastosowaniem Twojego skryptu do obsługi wyjątków rzuca po prostu mi ten FAIL bez stack trace
markuz
  1. <?php
  2.  
  3. set_error_handler(function ($severity, $message, $file, $line) {
  4. if (!(error_reporting() & $severity)) {
  5. // This error code is not included in error_reporting
  6. return;
  7. }
  8.  
  9. throw new ErrorException($message, 0, $severity, $file, $line);
  10. });
  11.  
  12. while(true) {
  13. try {
  14. $content = file_get_contents('http://forum.php.pl/test.php');
  15. } catch (Exception $e) { echo "FAIL" . PHP_EOL; }
  16. echo memory_get_usage(true) . PHP_EOL;
  17. }


Kod
?  ~ php7.1 test.php                
FAIL
2097152
FAIL
2097152
FAIL
2097152
FAIL
2097152
FAIL
2097152
FAIL
2097152
FAIL
2097152
FAIL
2097152
FAIL
2097152
FAIL
2097152
?  ~ php7.0 test.php
FAIL
2097152
FAIL
2097152
FAIL
2097152
FAIL
2097152
FAIL
2097152
FAIL
2097152
FAIL
2097152
FAIL
2097152


7.2 nie mogę teraz zainstalować, także sprawdzę później.

Ale już przy większej ilości interacji coś wzrasta:

Kod
FAIL 264
2097152
FAIL 265
2097152
FAIL 266
4194304

SmokAnalog
Pobawiłem się trochę Twoim i moim przykładem. Wniosek jest taki, że memory_get_usage(true) u mnie też pokazuje stałą wartość, ale już memory_get_usage() nie.
markuz
Bez wyjątków nie pobiera więcej pamięci:

  1. <?php
  2.  
  3. $n = 0;
  4.  
  5. while(true) {
  6. $content = @file_get_contents('http://forum.php.pl/test.php');
  7. echo $n++ . ' ' . memory_get_usage(true) . PHP_EOL;
  8. }


Kod
789 2097152
790 2097152
791 2097152
792 2097152
793 2097152
794 2097152
795 2097152
796 2097152
797 2097152
SmokAnalog
I to też jest bardzo ciekawa obserwacja. Masz jakiś pomysł jak to ugryźć? Ten projekt to nie jest sprawa życia i śmierci, ale bardzo mnie ciekawi ten problem i wolałbym zachować wyjątki. Jak nic nie wymyślimy, to zapytam na Stack Overflow.
markuz
Nie mam pomysłów. Wrzuć link do stackoverflow jak zadasz pytanie smile.gif
SmokAnalog
Temat już wisi. Nie będę linkował, ale podzielę się odpowiedzią jak tylko się pojawi.
com
  1. <?php
  2. set_error_handler(function ($severity, $message, $file, $line) {
  3. if (!(error_reporting() & $severity)) {
  4. // This error code is not included in error_reporting
  5. return;
  6. }
  7.  
  8. throw new ErrorException($message, 0, $severity, $file, $line);
  9. });
  10.  
  11. $n = 0;
  12.  
  13. while (true) {
  14. try {
  15. $content = file_get_contents('http://forum.php.pl/test.php');
  16. } catch (Exception $e) {
  17. echo "FAIL" . PHP_EOL;
  18. unset($e);
  19. }
  20. echo $n++ . ' ' . memory_get_usage(true) . PHP_EOL;
  21. }

Kod
FAIL
855 2097152
FAIL
856 2097152
FAIL
857 2097152
FAIL
858 2097152
FAIL
859 2097152
FAIL
860 2097152
FAIL
861 2097152
FAIL
862 2097152
FAIL
863 2097152
SmokAnalog
@com zobacz, co pisaliśmy wyżej. Bez unset też będziesz miał stałą wartość.
com
SmokAnalog ale unset jest na Exception, nie ma stałej wartości przy 220(u mnie/266 u kolegi markuz) mamy już 2*2097152, potem 4*2097152 itd. Problem polega na tym, że PHP trzyma referencje do wszystkich poprzednich Exception na stosie i trzeba je wykasować to pamieć nie rośnie zobacz doleciałem do pozycji 863 wink.gif
SmokAnalog
Chyba rzeczywiście jest coś w tym co mówisz, ale źle to argumentujesz. Zrobiłem testy i usunięcie unset nic nie zmienia. Jest za to jedno "ale" - różnica w ilości pamięci pojawia się, gdy użyjemy memory_get_usage() zamiast memory_get_usage(true). Tutaj rzeczywiście unset($e) powoduje, że pamięć nie rośnie, podczas gdy bez unset rośnie.

Pytanie jeszcze o co chodzi z tym parametrem w memory_get_usage, bo wg dokumentacji jest to:

Cytat
real_usage
Set this to TRUE to get total memory allocated from system, including unused pages. If not set or FALSE only the used memory is reported.


W praktyce ustawienie go na true nie uwzględnia wielkości stosu, więc albo im się coś popieprzyło, albo ja czegoś nie rozumiem.
com
przy
  1. <?php
  2. set_error_handler(function ($severity, $message, $file, $line) {
  3. if (!(error_reporting() & $severity)) {
  4. // This error code is not included in error_reporting
  5. return;
  6. }
  7.  
  8. throw new ErrorException($message, 0, $severity, $file, $line);
  9. });
  10.  
  11. $n = 0;
  12.  
  13. while (true) {
  14. try {
  15. $content = file_get_contents('http://forum.php.pl/test.php');
  16. } catch (Exception $e) {
  17. echo "FAIL" . PHP_EOL;
  18. unset($e);
  19. }
  20. echo $n++ . ' ' . memory_get_usage() . PHP_EOL;
  21. }


Kod
582 366144
FAIL
583 366144
FAIL
584 366144
FAIL
585 366144
FAIL
586 366144
FAIL
587 366144
FAIL
588 366144


Ten parametr w zasadzie nic nie zmienia, tylko mamy trochę większe zużycie ale ono nie rośnie dzięki temu że kasujemy referencje do poprzedniego.

Cytat
It is because exceptions include a backtrace, containing all the arguments given to the error handling closure. The fifth argument of ErrorException given is $context, an array containing all local variables, including the previous $e.


Cytat
Pytanie jeszcze o co chodzi z tym parametrem w memory_get_usage, bo wg dokumentacji jest to:


W źródle dokładnie to wygląda tak:
Kod
if (real_usage) {
        return AG(mm_heap)->real_size;
} else {
        size_t usage = AG(mm_heap)->size;
        return usage;
}
SmokAnalog
Cytat(com @ 6.02.2018, 19:56:49 ) *
Ten parametr w zasadzie nic nie zmienia, tylko mamy trochę większe zużycie ale ono nie rośnie dzięki temu że kasujemy referencje do poprzedniego.

Jak się dokładniej przyjrzałem, to ten parametr z true zwraca o wiele większą ilość pamięci (true: 2 MB, false: niecałe 400 KB).

Daję Ci punkciki Pomógł, bo rzeczywiście naprowadziłeś mnie na rozwiązanie problemu, a właściwie udowodniłeś to, co sam podejrzewałem. No to teraz już wiem jak sprawić, żeby mój crawler się nie dławił biggrin.gif
com
ha faktycznie ślepy jestem 2097152 > 366144 biggrin.gif No tak najistotniejsze jest pozbyć się starego $e biggrin.gif

Twórcy PHP twierdza, że nie jest to bug biggrin.gif
Wszytko przez to, że Exception ma taka metodę:
  1. $e->getPrevious();
SmokAnalog
Dzisiaj całą noc chodził crawlerek bez żadnej zadyszki smile.gif
com
super biggrin.gif ciekawe co tam crawlujesz tongue.gif
darko
Cytat
It is because exceptions include a backtrace, containing all the arguments given to the error handling closure. The fifth argument of ErrorException given is $context, an array containing all local variables, including the previous $e.

Hmm... pół giga tekstu backtrace'u dla bieżącego i poprzedniego wyjątku + zawartość zmiennych lokalnych. To ile łącznie wyjątków zostało rzuconych? Ciężko w to uwierzyć, że to jest faktyczna przyczyna problemu.
SmokAnalog
Czy ja wiem czy takie dziwne?

Zrobiłem jeszcze jeden test:

  1. if ($fails === 100) {
  2. var_dump(get_defined_vars());
  3. $dump = preg_replace('# {2,}#', ' ', str_replace(PHP_EOL, ' ', ob_get_clean()));
  4. file_put_contents($fails.'.txt', $dump);
  5. }


Mam dane dla różnych liczb faili i w zależności czy unset był włączony czy nie:

  • 1 fail, włączony unset: 6 KB
  • 10 faili, włączony unset: 6 KB
  • 100 faili, włączony unset: 6 KB
  • 1 fail, wyłączony unset: 12 KB
  • 10 faili, wyłączony unset: 72 KB
  • 100 faili, wyłączony unset: 673 KB


Czyli przy stu failach, var_dump z usuniętym nadmiarem białych znaków ma 673 KB. Każdy fail dodaje do pamięci na przykład pełną informację o $_SERVER, a to swoje waży.

Co więcej, w moich testach są tylko nieudane file_get_contents. Zauważyłem, że w pamięci nie ma wcale HTML-a z tych nieudanych, tylko właśnie z poprzedniej wartości - tej udanej. Czyli w moim teście HTML-a nie było wcale w pamięci.

W kolejnym teście zrobiłem tak, że naprzemiennie występuje prawidłowy i nieprawidłowy URL. I tutaj uwaga! Przy braku unset i zaledwie 10-ciu failach, rozmiar pliku wzrósł z 72 KB do... 6 MB! Właśnie dlatego, że dla każdego wyjątku była doklejona poprzednia wartość $html.

Wniosek: najlepszym rozwiązaniem wydaje się rzeczywiście unset($exception), ale unset($html) też w dużym stopniu pomaga. Ten pierwszy sprawia, że zużycie pamięci w ogóle nie rośnie z kolejnymi failami, a ten drugi sam z siebie zmniejsza znacznie pamięć zabieraną przez wyjątki, ale zużycie nadal rośnie i w końcu się przepełni.

@darko jak widać ilość pamięci zabieranej przez wyjątki jest tu na tyle duża, że nie ma co wątpić. Przy setkach tysięcy iteracji to się niestety zsumuje do tych 500 MB, nawet z unset($html).
com
Ale jest, bo nie wywołany jest destruct na starym Exception wiec, gc nie może sprzątać, bo jak pamiętamy PHP zlicza referencje biggrin.gif
darko
Jeśli masz znaczną ilość faili, to może przed próbą pobrania zawartości strony odczytaj samą wartość nagłówka HTTP i sprawdzaj czy to nie 404. mam na myśli coś takiego, pobierasz tylko nagłówki odpowiedzi serwera i wyciągasz sam kod http odpowiedzi:

  1. protected function _getHeaderResponseCode($url) {
  2. $handle = curl_init($url);
  3. curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
  4. curl_setopt($handle, CURLOPT_NOBODY, 1);
  5. $response = curl_exec($handle);
  6. $httpCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
  7. return $httpCode;
  8. }
  9.  
  10. // ...
  11.  
  12. if ($this->_getHeaderResponseCode($url) != 404) {
  13. $data = file_get_contents($url);
  14. }
  15.  
  16. // ...
SmokAnalog
Darko, nie obraź się, ale wszystkie Twoje odpowiedzi w tym temacie (łącznie z powyższą) są idiotyczne. Sugerujesz teraz, żeby spowolnić cały crawler dwukrotnie tylko po to, żeby uniknąć wyjątku? Przecież wiadomo, że wąskim gardłem wszelkich webowych crawlerów jest czas odpowiedzi serwera zewnętrznego. Poczytaj ten temat i inne podobne tematy, żeby poznać konsolowe i crawlerowe zastosowanie PHP. Ubolewam nad tym, że przez całą noc crawluję zaledwie 20-30 tys. wyników, ale nieeee - spowolnię sobie to do 10-15 tys., żeby nie wyrzucać wyjątków. Yeah!

Jakbym już miał unikać wyjątków, to przecież mogę od razu użyć cURL-a, który wyjątków dla 404 nie wywala. Chciałem uprościć kod i nauczyć się czegoś więcej o zarządzaniu pamięcią w sytuacji wyrzucania wyjątków.
darko
To teraz Ty się nie obraź, bo już mnie lekko irytujesz swoją impertynencją w tym i nie tylko w tym temacie. Najpierw sam sobie tworzysz wyimaginowany problem i wprowadzasz ludzi, którzy chcą się wspólnie zmóżdzyć z Tobą w błąd, nie podając pełnego kodu, a jedynie jakieś wyrywki. Pewnie. Domyślamy się wszyscy, że masz swój error handler... Patrz! unset zwalnia pamięć! Eureka! Następnie drążysz temat zarządzania pamięcią, na które to dość specyficzne w PHP zarządzanie nie masz totalnie żadnego wpływu i głową muru nie przebijesz, by na końcu określić czyjąś wypowiedź jako idiotyczną. Świetnie. Bawmy się tak dalej.
Użyj curla i nie płacz, że wolno działa, albo, że skrypt zżera za dużo pamięci. Przez podejście takich ludzi, jak Ty, o społeczności skupionej wokół języka PHP jeszcze długo będzie się mówić źle i tylko źle.
Zamiast skupić się na użyteczności i przydatności kodu, a przede wszystkim na jego wartości biznesowej, lecisz na forum ze sztucznie stworzonym problemem, którego tak naprawdę nie ma.
Założę się, że nawet nie raczyłeś sprawdzić curlowego rozwiązania, ale z góry zakładasz, że będzie działało znacznie wolniej.
Poczytaj o curl_multi_init, curl_multi_exec, wykonywaniu jednoczesnym żądań korzystając z tzw. gniazd nieblokujących, asynchronicznie. Jakby to dobrze napisać, to ten Twój crawlerek będzie zapierdzielał jeszcze szybciej niż z kupą niepotrzebnych wyjątków i skopiowanym całym backtracem zupełnie niepotrzebnie. Jak znasz odrobinę języka C to polecam lekturę źródeł PHP, tam naprawdę więcej nauczysz się o zarządzaniu pamięcią, niż poprzez takie eksperymenty.
Zresztą - nawet jeśli ten crawler będzie ciut wolniejszy, to chyba lepiej troszkę wolniej, ale niech w ogóle działa i nie wali błędami niż crawler, który po prostu nie działa...
PHP jeszcze Cię wiele razy zaskoczy. Idiotyczne są takie pseudo problemy. Tyle z mojej strony, trzymaj się, buziaki, pozdrówki. ps. nie jesteś w stanie mnie obrazić.
SmokAnalog
No ciekawe jak przyśpieszysz w kodzie crawlera przysyłanie odpowiedzi ze źródła, na które nie masz wpływu. Coś tam Ci świta z zarządzaniem pamięci, ale sam się gubisz. Jeszcze przed chwilą mówiłeś, że PHP się nie nadaje do daemonów i crawlerów, co jest kompletną bzdurą.

Akurat unset w catch nie jest powszechnie znaną techniką, bo nie jest oczywistym faktem to co się dzieje z wyjątkami w pamięci.

Lepiej być impertynenckim niż niekompetentnym i wypowiadać się w tematach, o których się nie ma pojęcia, zamiast grzecznie czekać na odpowiedź kogoś, kto się zna.

A problem nie jest wyimaginowany. Bardzo często chce się zachować strukturę z wyjątkami i dobrze jest wiedzieć jak zaradzić zaśmiecaniu pamięci w pętli try..catch.

Najpierw mówiłeś coś innego, teraz mówisz coś innego, więc grzecznie przyznaj się do błędu zamiast teraz udawać eksperta w tym temacie.
vokiel
Cytat(SmokAnalog @ 9.02.2018, 23:52:51 ) *
Darko, nie oUbolewam nad tym, że przez całą noc crawluję zaledwie 20-30 tys. wyników,


Jeśli to za mało to mam kilka propozycji
* cURL zamiast file_get_contnets (w wielu wypadkach cURL jest szybsze, więcej rzeczy można ustawić)
* curl_multi - aby zwielokrotnić ilość pobieranych zasobów
* kilka procesów na raz

Prosty przykład:
  1. <?php
  2.  
  3. for($i = 259010; $i<259310; $i++){
  4. $url = 'http://forum.php.pl/index.php?showtopic=' . $i;
  5. $html = file_get_contents($url);
  6. $usage = memory_get_usage();
  7. $usageTrue = memory_get_usage(true);
  8. echo date('H:i:s').'.'.gettimeofday()['usec'].' | ID: '.$i.' length: '.strlen($html).' | usage: '.$usage.' | usage(true) '.$usageTrue.PHP_EOL;
  9. }


Test (obcięte wyniki do dwóch pierwszych i ostatnich)

  1. $ time php file_get_contents.php
  2.  
  3. 13:48:46.202671 | ID: 259010 length: 221566 | usage: 592840 | usage(true) 2097152
  4. 13:48:46.291102 | ID: 259011 length: 40984 | usage: 414280 | usage(true) 2097152
  5. ..
  6. 13:49:54.433896 | ID: 259308 length: 38852 | usage: 410184 | usage(true) 2097152
  7. 13:49:54.573305 | ID: 259309 length: 76766 | usage: 447048 | usage(true) 2097152
  8.  
  9. real 1m8.822s
  10. user 0m0.193s
  11. sys 0m0.284s


Wersja na curl:
  1. <?php
  2.  
  3. for($i = 259010; $i<259310; $i++){
  4. $url = 'http://forum.php.pl/index.php?showtopic=' . $i;
  5.  
  6. $ch = curl_init();
  7. curl_setopt($ch, CURLOPT_URL, $url);
  8. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  9. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
  10. curl_setopt($ch, CURLOPT_HEADER, 0);
  11. $html = curl_exec($ch);
  12. curl_close($ch);
  13.  
  14. $usage = memory_get_usage();
  15. $usageTrue = memory_get_usage(true);
  16. echo date('H:i:s').'.'.gettimeofday()['usec'].' | ID: '.$i.' length: '.strlen($html).' | usage: '.$usage.' | usage(true) '.$usageTrue.PHP_EOL;
  17. }
  18.  


  1. $ time php curl.php
  2.  
  3. 13:50:54.645658 | ID: 259010 length: 221568 | usage: 594096 | usage(true) 2097152
  4. 13:50:54.875325 | ID: 259011 length: 40984 | usage: 415536 | usage(true) 2097152
  5.  
  6. 13:51:41.684053 | ID: 259308 length: 38852 | usage: 411440 | usage(true) 2097152
  7. 13:51:41.888811 | ID: 259309 length: 76766 | usage: 448304 | usage(true) 2097152
  8.  
  9. real 0m48.066s
  10. user 0m0.227s
  11. sys 0m0.270s


A bez curl_setopt($ch, CURLOPT_HEADER, 0); trwało porównywalnie do wersji z file_get_contents:
  1. real 0m56.110s
  2. user 0m0.176s
  3. sys 0m0.185s


Wersja z curl_multi
  1. <?php
  2.  
  3. $urls = [];
  4. for($i = 259010; $i<259310; $i++){
  5. $urls[] = $i;
  6. }
  7.  
  8. $ch_multi = curl_multi_init();
  9. $ch_arrr = [];
  10.  
  11. $options = [
  12. CURLOPT_RETURNTRANSFER => true,
  13. CURLOPT_FOLLOWLOCATION => true,
  14. CURLOPT_MAXREDIRS => 3,
  15. CURLOPT_HEADER => 0,
  16. CURLOPT_CONNECTTIMEOUT => 3,
  17. ];
  18.  
  19. for ($i = 0; $i < 2; $i++){
  20. $ch = curl_init();
  21. $options[CURLOPT_URL] = 'http://forum.php.pl/index.php?showtopic='.$urls[$i];
  22. curl_setopt_array($ch, $options);
  23. curl_multi_add_handle($ch_multi, $ch);
  24. }
  25.  
  26. do {
  27. while (($execrun = curl_multi_exec($ch_multi, $running)) == CURLM_CALL_MULTI_PERFORM);
  28. if($execrun != CURLM_OK){
  29. break;
  30. }
  31.  
  32. while ($done = curl_multi_info_read($ch_multi)) {
  33. $html = curl_multi_getcontent($done['handle']);
  34.  
  35. $usage = memory_get_usage();
  36. $usageTrue = memory_get_usage(true);
  37. echo date('H:i:s').'.'.gettimeofday()['usec'].' | ID: '.$urls[$i].' length: '.strlen($html).' | usage: '.$usage.' | usage(true) '.$usageTrue.PHP_EOL;
  38.  
  39. ++$i;
  40. if (!empty($urls[$i])) {
  41. $ch = curl_init();
  42. $options[CURLOPT_URL] = 'http://forum.php.pl/index.php?showtopic='.$urls[$i];
  43. curl_setopt_array($ch, $options);
  44. curl_multi_add_handle($ch_multi, $ch);
  45. }
  46. curl_multi_remove_handle($ch_multi, $done['handle']);
  47. }
  48. } while ($running);
  49.  
  50. curl_multi_close($ch_multi);


  1. $ time php curl_multi.php
  2. 13:55:01.296270 | ID: 259011 length: 40987 | usage: 442440 | usage(true) 2097152
  3. 13:55:01.425666 | ID: 259012 length: 41189 | usage: 444104 | usage(true) 2097152
  4.  
  5. 13:55:33.281197 | ID: 259308 length: 76766 | usage: 518152 | usage(true) 2097152
  6. 13:55:33.370113 | ID: 259309 length: 38852 | usage: 518112 | usage(true) 2097152
  7.  
  8. real 0m32.255s
  9. user 0m20.641s
  10. sys 0m11.609s


Podsumowanie
* file_get_contents: 1m8.822s
* curl: 0m48.066s
* curl_multi: 0m32.255s
darko
Cytat(SmokAnalog @ 10.02.2018, 03:52:36 ) *
Jeszcze przed chwilą mówiłeś, że PHP się nie nadaje do daemonów i crawlerów, co jest kompletną bzdurą.
Najpierw mówiłeś coś innego, teraz mówisz coś innego, więc grzecznie przyznaj się do błędu zamiast teraz udawać eksperta w tym temacie.

Błędu? Jakiego błędu? Ja podtrzymuję to, co napisałem. Do napisania wydajnego crawlera ludzie wybierają inne niż php narzędzia: perl, python, javę, a nawet basha czy node.js. Rozumiem, że Ty jesteś z tych, co uważają, że programowanie zaczyna się w momencie napisania pierwszej linii kodu. Dla mnie programowanie to najpierw dogłębna analiza problemu i dobranie optymalnego (czyt. najlepszego) narzędzia do danej sytuacji, a nie pisanie w tym, w czym wydaje Ci się, że umiesz to zrobić i jakoś to będzie. Jak widać, jakoś to nie będzie. Jakoś to nie jakość, a forum jest od rozwiązywania problemów, a nie od ich mnożenia. Vokiel już Ci podał na tacy kilka możliwych rozwiązań, ale pewnie i tak będziesz się upierał przy swoim, że po co, że na co tak itd. Kończę tę jałową przepychankę, bo pojawiają się z Twojej strony argumenty z półki ad personam, co nigdy dla poważnej rozmowy niczego dobrego nie wróżyło. Ponownie pozdrawiam Cię i mimo wszystko życzę Ci powodzenia i owocnej nauki. Miłego dnia.
phpion
Panowie, nie ma się co tak napinać. Każdy z Was wnosi konkrety do dyskusji nawet jeśli zdania są podzielone. Sam z ciekawością czytam wypowiedzi jednego i drugiego. Można się spierać ale trzymajmy poziom, osobiste wycieczki są daremne. Nie jesteście zapewne nastolatkami więc powinniście podejść do dyskusji na odpowiednim poziomie. Każda wypowiedź jest cenna i tego się trzymajmy. Żaden z Was chyba nie pisze po to żeby dogryźć drugiemu. Nie ma co być uszczypliwymi.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.