![]() |
Jag håller på att parsar handhistoriefiler för poker och efter mycket slit så har jag fått ihop ett system som fungerar bra, men med ett mycket besvärligt undantag.
Då text filerna blir stora så dör skriptet innan det är klart. Filerna är inte jättestora och problemet visar sig innan de nått 100k. Systemet är en state machine där jag använder file_get_contents för att läsa in hand för hand (en 20-30 rader åt gången) för att sedan köra en loop med olika "states" som har diverse reguljära uttryck för att extrahera informationen. Informationen sparas sedan i en array och då en hand är färdig parsad sparas den i en MySQL databas. Sedan rensar jag arrayerna och börjar på nästa hand. Enligt memory_get_usage så använder skriptet drygt 5 Mb då det dör. Jag har testat att höja memory limit till 100M, men det gör ingen skillnad. Skriptet jobbar bara i några sekunder innan det dör och timeout är satt till 120s. PHP ger inte ger mig inte någonting att gå på vad det är som får skriptet att dö, varken varningar eller felmeddelanden. I firefox kör skriptet en 4 sekunder och sedan dyker det upp en "connection reset" sida. I IE dyker det upp en "page not available" sida efter någon sekund. Det bästa hade väl varit att läsa linje för linje, men det blir lite knepigt att ändra i detta stadium med det system jag använder. Och man tycker att en 20-30 linjer åt gången inte borde vara några problem alls. Är det några resursinställningar i PHP eller apache som jag kan fixa problemet? Koden jag använder för att läsa in en hand från textfilen är: Kod:
file_get_contents($filename, FALSE, NULL, $offset, $end) |
Vad säger php-error-loggen?
|
Ingenting i varken PHP eller apache loggar, och jag kör med E_ALL.
|
Hur körs det? Som Apache modul, CGI eller som CLI script?
CLI är mer eller mindre känt att vara en instabil variant. Lägg in breaks i scriptet? Anpassa scriptet så att den inte gör om arbetet när det kraschar utan fortsätter där det slutade. Kan vara att scriptet får något oväntat svar? Hur hämtar du indata? Från en lokal fil? Från en annan server? Lite mer info så sätt kan behövas. |
Lokalt så kör jag det med en oförändrad xampp installation på XP och det är väl som apache modul.
Jag får samma problem på mitt webhotell som kör med linux och som CGI. Jag har ingen aning om hur jag skall lyckas få skriptet att börja om där det dog. PHP har ju slutat exekvera det och jag kan inte komma på någon metod för att känna av det och starta om processen. Indata är från en lokal fil. |
Jag missade....angående breaks så kör ju koden som sagt hand för hand och normalt har jag Charles debugging proxy på och då så försvinner inte sidan utan slutar bara ladda vid en viss punkt.
Så jag kan följa exakt var det dör och det har inte att göra med kod eller input vid den punkten. Tar jag bort några rader i början av filen så parsar den några rader till, så det förefaller helt klart vara någon slags resursbegränsing. |
Stäng av max_execution_time, höj minnes användning, Finns en hel del man kan ändra i php.ini, gör ändringarna där istället för i scriptet och starta om apache.
Eka även ut data i webläsaren... |
Har testat följande i php.ini
pcre.backtrack_limit = 1000000 pcre.recursion_limit = 1000000 ignore_user_abort = On max_execution_time = 120 max_input_time = -1 memory_limit = -1 Ingen av ändringarna har någon inverkan på hur lång skriptet kommer innan det dör. pcre.recursion_limit verkar krasha apache vid den extrema inställningen men då den inte gör det har den ingen effekt på hur långt skriptet kör. |
Jag märkte nu att om jag inte skriver ut all data utan bara ett id för varje hand så dör skriptet ungefär dubbelt så tidigt.
Det tyder kanske på att det inte är en resurs som tar slut då jag inte ser varför den skulle använda mer resurser för att man tar bort en massa echo's? Jag har dock fortfarande inte en aning vad det är som dödar skriptet :( |
Har du testat köra skriptet utan att köra det igenom Apache?
|
Jag testade nu att köra i CLI...är det det du menar?
Resultat...det händer ingenting. Tar en knapp sekund och sedan är jag tillbaka till prompten. Jag hittar inte heller några kommandon som verkar som de skulle ge mig mer information. |
Jag tycker det låter som om php är tilldelat för lite minne för uppgiften.
Priva att ändra detta i php.ini och se om det tar längre tid innan det "dör". |
Den enda minnesinställningen jag känner till är memory_limit i php.ini, och den gör som sagt ingen skillnad.
|
-Testa att minimiera kommandona i din huvudloop, t.ex. ta bort mysql anropen och se om det fungerar.
-Prova att använda fopen() och fgets() (eller fread()) istället för file_get_contents(), eftersom file_get_contents() använder memory-map teknik för att läsa in filen kan det vara värt att testa en variant utan memory mapping. |
släng in
error_reporting(E_ALL); ini_set('display_errors', 'On'); längst upp i php-filen. Ibland får man tvinga php att felrapportera. |
Tack för tipsen.
Jag har testat tidigare att deaktivera databasdelen samt att inte lagra datan i en array, men samma problem. fgets är inte så enkelt att ändra till då systemet skall läsa hand för hand och inte linje för linje vilket kanske hade varit bäst....men jag skulle gärna undvika att skriva om såpass mycket. Jag har testat att läsa in hela filen med fopen och med cURL och sedan använda substr för att plocka ut relevant hand. Skriptet dör på precis samma ställe men minnesanvändningen är högre. Jag har E_ALL och E_STRICT. Faktum är att om jag låter skriptet köra tills det dör så dyker det inte ens upp i access.log |
Det är två problem. Dels fungerar inte ditt script när du ökar på mängden indata. Dels får du inte bra loggar där du tycker att du ser allt.
Det finns en funktion som heter memory_get_peak_usage. Den kanske kan hjälpa dig att få grepp om minnesförbrukningen. |
Jag ser absolut ingenting i loggar eller output vilket sannerligen är ett problem i detta fall....någon slags indikation på varför skriptet dör vorde mycket hjälpsamt, om inte annat för att jag då skulle haft något att googla med för att få mer insikt om problemet.
Jag har testat memory_get_peak_usage och det ligger på ungefär samma som memory_get_usage, d.v.s. drygt 5Mb. |
Testa att läsa betydligt fler än 1 hand/gång, problemet verkar inte vara att minnet tar slut, utan snarare att nått anrop krashar efter att det itererats för många gånger, som t.ex file_get_contents.
Posta källkoden till din huvudloop så blir det lättare att försöka hitta felet. |
Det är inte en huvudloop direkt utan tre klasser som interagerar, annars hade jag redan postat den, men det blir en hel del kod som kan vara relevant.
HHParser.php är huvudklassen som kollar vad det är för format på text filen (vilket pokernätverk den kommer ifrån). Den relevanta koden där är: Kod:
function getFormat(){ Kod:
<? Kod:
$this->C->Parser->FSM('/(Seat[\r\n'.$this->playerNameChars.':.$,]+\))+/',$this, 'TablePlayers', 'string', 'HANDFORCED','TABLEPLAYERS', NULL, $streets[0]); Då alla states är genomgångna så börjar ft.php om från början och bearbetar nästa hand på samma vis. Tyvärr är det som du ser inte alltför enkelt att ändra så man läser mer eller mindre än en hand åt gången. Jag kan förvisso läsa in hela filen i ParseFile funktionen i parserclass, men jag måste ändå plocka ut reveant del någonstans. Resultatet då jag har testat att läsa in hela filen och använda substr för att plocka ur nödvändig del är att den kör lika långt men använder mer minne. |
Du måste lägga in fler felkontroller på fler ställen, t.ex:
var kollar du returnvärdet i Parse() funktionen? returnvärdet på file_get_contents() ? Ifall returnvärdet blir FALSE kommer Parse() raden: $LEN=strlen($text) bli helt fel, som ett exempel... ini_set() behövs bara sättas en gång, som det är nu sätts det varje gång ft.php kallar ParseFile().. Lägg in flera echo så du får koll på värdena under resans gång. |
Tack för tipsen Simon.
Visst, felhanteringen är lite bristfällig för tillfället. Det är lite bökigare att implementera då skriptet anropas från en flash fil och jag kan inte ha någon output annat än från PHP filen som instansierar klasserna, så t.ex. die() eller exit() går tyvärr bort. Jag debuggar i webbläsaren med echo just nu och kommer väl lägga till lite variabler jag kan följa för att ge detaljerad information till flash klienten om eventuella fel då systemet är live. Returvärdet på Parse() är egentligen inte till mycket nytta. Jag anger alltid ett state att gå vidare till eller ett stopp och jag har inte ens behövt köra echo på det för att kolla då det inte varit några frågetecken där. Att filen finns kollar jag innan parsern körs, men det skadar kanske inte att dubbelkolla. ini_set slängde jag bara in lite slarvigt där för ett hastigt test, men det fyller ingen funktion och skall väck. Jag har kört en hel del echo's men plockade bort dem innan jag postade koden. Hursomhelst verkar det som det inte är något direkt fel med koden som orsakar problemet. Uppenbarligen är den ineffektiv eller problematisk på något sätt eftersom jag har detta problemet, men den exekverar utan problem, skapligt snabbt och utan alltför mycket minnesanvändning då jag testat med massor med olika input filer. Det är bara att den dör efter ett visst antal händer :( En sak jag märkt gör att den klarar några händer till är om jag kör flush() efter varje hand. Egentligen skall inte skriptet generera någon output, och några händer extra gör ingen större skillnad i praktiken, men det kanske kan vara en ledtråd vad det är som spökar? |
Testa lägg in typ: usleep(100000) ibörjan i ParseFile() funktionen, för att slöa ner det en en tiondelssekund och se om det blir nån skillnad.
Eftersom file_get_contents både öppnar och stänger filen kanske inte OS:et hinner stänga den innan nästa anrop. Jag vet att man kan få mkt konstiga fel i PHP ifall man gör snabba itererande filoperationer på samma fil. |
Jag har testat att köra sleep mellan varje hand och nu har jag även ändrat så att jag läser in hela filen en gång och sparar den i en variabel och sedan plockar ut relevant bit med substr istället.
Som vanligt kör skriptet precis lika långt. Jag har även funderat på om man kunde splitta upp filen i flera delar och ominstansiera klasserna för varje del, men jag kommer inte på något sätt att smidigt lösa det. Att köra unset() på en klass funkar ju inte...finns det någon funktion för att ta bort en klass? |
Alla tider är GMT +2. Klockan är nu 16:33. |
Programvara från: vBulletin® Version 3.8.2
Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
Svensk översättning av: Anders Pettersson