Check alle échte Black Friday-deals Ook zo moe van nepaanbiedingen? Wij laten alleen échte deals zien

[PHP] while-loop met fgetcsv onverklaarbaar lang bezig

Pagina: 1
Acties:

  • Pmleader
  • Registratie: Februari 2012
  • Laatst online: 11-11 12:47
Goedemiddag allemaal,

Ik ben bezig met een hobby projectje om de stats van een spel bij te houden voor een hele clan(~500 man).
Nu zijn er zat manieren om de raw data op te vragen in csv's dus dat is het probleem niet.

Ik heb nu echter een vreemd probleem bij het lezen van een csv.
De csv bevat maximaal 501 lines met een maximale linelengte van 300 chars, niets bijzonders dus.
Echter als ik de csv line voor line afwerk en opsla in de database kost dat ruim 30 seconden runtime van het script. Dit lijkt mij niet goed.
Daarnaast is het zo dat de gegevens zijn inserted in de database na een seconde of 3-4... dus wat is dat script de rest van de tijd aan het doen?!

het script gaat als volgt:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?php
$start = microtime(true);
$mysqli = new mysqli("localhost", "user", "pass", "tracker_test");

/* check connection */
if (mysqli_connect_errno()) {
    printf("Connect failed: %s\n", mysqli_connect_error());
    exit();
}
$dbconnect = microtime(true);
if (($handle = fopen("http://services.runescape.com/m=clan-hiscores/members_lite.ws?clanName=Overloaded+XP", "r")) !== FALSE) {
    $csvget = microtime(true);
    $nn = 0;
    $naam='';
    $rank='';
    $xp=0;
    $kills=0;
    if (!($stmt = $mysqli->prepare("INSERT INTO list_test(naam, rank, xp, kills) VALUES (?, ?, ?, ?)"))) {
        echo "Prepare failed: (" . $mysqli->errno . ") " . $mysqli->error;
    }
    if (!$stmt->bind_param("ssii", $naam, $rank, $xp, $kills)) {
        echo "Binding parameters failed: (" . $stmt->errno . ") " . $stmt->error;
    }
    while (($data = fgetcsv($handle, 255, ",")) !== FALSE) {
        # Count the total keys in the row.
        $c = count($data);
        if ($nn > 500){
            break;
        }
        if ($nn > 0) {
            for ($x = 0; $x < $c; $x++) {
                if ($x == 0) {
                    $naam = $data[$x];
                }
                if ($x == 1) {
                    $rank = $data[$x];
                }
                if ($x == 2) {
                    $xp = $data[$x];
                }
                if ($x == 3) {
                    $kills = $data[$x];
                }
            }
            # execute statement
            if (!$stmt->execute()) {
                echo "Execute failed: (" . $stmt->errno . ") " . $stmt->error;
            }
        }

        $nn++;
    }
    $stmt->close();
    echo $nn . " lines parsed.";
    $done = microtime(true);
    # Close the File.
    fclose($handle);
}
echo "<br><br>";
$end = microtime(true);
echo ($end - $start) * 1000 . " ms totaal";
echo "<br><br>";
echo ($dbconnect - $start) * 1000 . " ms db connection";
echo "<br><br>";
echo ($csvget - $start) * 1000 . " ms csv ophalen";
echo "<br><br>";
echo ($done - $start) * 1000 . " ms csv verwerken";
?>


output:
code:
1
2
3
4
5
6
7
8
9
469 lines parsed.

31531.750917435 ms totaal

3.9360523223877 ms db connection

963.66000175476 ms csv ophalen

31531.699895859 ms csv verwerken


Er zit wellicht nog wat rommel in de code, ik ben echt al een paar dagen hiermee bezig en google biedt helaas geen oplossing...

Ben ik nu ongelofelijk dom bezig?

Mvg,
Pmleader

  • Voutloos
  • Registratie: Januari 2002
  • Niet online
fgetcsv kan ook null teruggeven, dus je wilt wellicht zowel false als null correct afhandelen.

{signature}


  • Pmleader
  • Registratie: Februari 2012
  • Laatst online: 11-11 12:47
Als er een null terugkomt gaat de statement zeuren met een foutmelding bij execute.

  • Sircuri
  • Registratie: Oktober 2001
  • Niet online

Sircuri

Volledig Appelig

je doet een while loop over een open socket-handler naar een remote bestand. Ik kan me indenken dat dat niet erg snel zal gaan. daarnaast is die fgetcsv ook nog voorzien van functionaliteit om je regel te parsen en te converteren naar een array.

Wat je kan proberen is om de hele CSV in 1 keer in te lezen en dan pas met fgetcsv je bestand te verwerken. Wellicht dat daar namelijk je vertraging in zit.

Signature van nature


  • Pmleader
  • Registratie: Februari 2012
  • Laatst online: 11-11 12:47
Sircuri schreef op dinsdag 04 februari 2014 @ 16:59:
je doet een while loop over een open socket-handler naar een remote bestand. Ik kan me indenken dat dat niet erg snel zal gaan. daarnaast is die fgetcsv ook nog voorzien van functionaliteit om je regel te parsen en te converteren naar een array.

Wat je kan proberen is om de hele CSV in 1 keer in te lezen en dan pas met fgetcsv je bestand te verwerken. Wellicht dat daar namelijk je vertraging in zit.
Ik heb nu gekeken hoe lang elke line doet over een execute:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
while (($data = fgetcsv($handle, 255, ",")) !== FALSE) {
        # Count the total keys in the row.
        $c = count($data);
        $rowstart = microtime(true);
        if ($nn > 500){
            break;
        }
        if ($nn > 0) {
            for ($x = 0; $x < $c; $x++) {
                if ($x == 0) {
                    $naam = $data[$x];
                }
                if ($x == 1) {
                    $rank = $data[$x];
                }
                if ($x == 2) {
                    $xp = $data[$x];
                }
                if ($x == 3) {
                    $kills = $data[$x];
                }
            }
            # execute statement
            if (!$stmt->execute()) {
                echo "Execute failed: (" . $stmt->errno . ") " . $stmt->error;
            }

        }
        $rowtime = microtime(true);
        echo ($rowtime - $rowstart) * 1000 . " ms voor row ".$nn."<br>";
        $nn++;
    }


Dit levert op dat elke row binnen 2ms afgehandeld is.(een enkele uitschieter naar 10ms)
Daaruit zou ik haast willen concluderen dat er toch iets anders gebeurd...

Wat stel jij voor om het bestand in 1 keer te lezen?

  • Sircuri
  • Registratie: Oktober 2001
  • Niet online

Sircuri

Volledig Appelig

je meet de tijd niet juist. Je meet nu de tijd van het verwerken van 1 regel data uit je CSV bestand. De aanroep naar "fgetcsv" staat in je WHILE clause en die meet je nu niet. Ik geloof inderdaad dat het inserten van de data in de database in 2 ms gaat.
Verder ken ik PHP niet goed genoeg om je een alternatief te geven.

PHP:
1
2
3
4
5
while (  <!-- hier moet ergens je  $rowstart = microtime(true); -->    ($data = fgetcsv($handle, 255, ",")) !== FALSE) { 
        # Count the total keys in the row. 
        $c = count($data); 
        $rowstart = microtime(true);  <-- en niet hier
        if ($nn > 500){

Signature van nature


  • w.l
  • Registratie: Mei 2007
  • Laatst online: 17-11 21:39

w.l

Het probleem ligt bij het gebruik van fopen en fgetcsv. Zie onderstaand script, het haalt alleen de data op maar loopt toch ongeveer even lang. Je gebruikt beter curl.

PHP:
1
2
3
4
5
6
7
8
9
10
11
<?php
$start = microtime(true); 
if (($handle = fopen("http://services.runescape.com/m=clan-hiscores/members_lite.ws?clanName=Overloaded+XP", "r")) !== FALSE) { 

    while (($data = fgetcsv($handle, 255, ",")) !== FALSE) { 
    } 
    fclose($handle);
}
$end = microtime(true); 
echo ($end - $start) * 1000 . " ms totaal";
?>

  • Pmleader
  • Registratie: Februari 2012
  • Laatst online: 11-11 12:47
Toch vind ik het vreemd dat de nieuwe records binnen 5 seconden in de mysql server zijn aangekomen, en dat het script vervolgens nog 25-30 seconden bezig is met "iets".

Daarbij word deze methode op alle sites(o.a. php.net) als aangewezen methode voor csv lezen beschreven.

  • MueR
  • Registratie: Januari 2004
  • Laatst online: 22-11 12:49

MueR

Admin Devschuur® & Discord

is niet lief

fopen wordt nooit aangeraden om files over het internet aan te spreken. Het kan wel, maar het is niet verstandig.

Anyone who gets in between me and my morning coffee should be insecure.


  • Pmleader
  • Registratie: Februari 2012
  • Laatst online: 11-11 12:47
Het is mij uiteindelijk gelukt door het bestand eerst te downloaden naar de server in een tijdelijk bestand.
Nu word het hele script in ongeveer 1 seconde doorlopen.

code:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
 * download clan list.
 */
$url  = 'http://services.runescape.com/m=clan-hiscores/members_lite.ws?clanName=Overloaded+XP';
$path = 'members_lite';

$fp = fopen($path, 'w');

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_FILE, $fp);

$data = curl_exec($ch);

curl_close($ch);
fclose($fp);


Hoop dat toekomstige mensen met problemen hier nog wat aan hebben.

Iedereen bedankt voor het meedenken.

  • Gomez12
  • Registratie: Maart 2001
  • Laatst online: 17-10-2023
Even nog ter mogelijke verklaring dan...

Wat je fopen doet is een complete inetconnectie opbouwen, dan gaat je fgetcsv daar alles lezen tot regel x (tot zover niets raars)

Maar wat er dan gebeurt is dat je fopen de data niet cached / streamt dus eigenlijk gewoon een socket openhoud terwijl je niets zit te doen (en dan besluit de server of je fopen commando om de socket maar te sluiten).
Waardoor als fgetcsv de 2e regel probeert te lezen er weer een connectie wordt opgebouwd, alles wordt van regel 1 tot 2 gelezen en regel 2 wordt verwerkt, dan is de connectie weer verbroken en rinse and repeat.

Dus in wezen haal je met 501 regels 501x het betand gedeeltelijk op en wordt er 501x een connectie opgebouwd. En dat kost allemaal tijd, die tijd is niet spannend als het 1x gebeurt, maar als het 501x gebeurt dan gaat die tijd wel tellen...

Zeg dat een http-handshake 10ms kost, dan ben jij dus 5010 ms kwijt aan http-handshakes en dan heb je nog geen regel uit het bestand gelezen. Dat is enkel maar tijd die benodigd is om een regel te gaan lezen.

Download / cache het bestand en je bent eenmalig 10ms kwijt aan een http-handshake, plus dat fgetcsv nu gewoon een pointer in het bestand kan bijhouden ipv dat het bestand elke keer opnieuw gelezen moet worden om een kunstmatige pointer te hanteren.

  • Pmleader
  • Registratie: Februari 2012
  • Laatst online: 11-11 12:47
Gomez12 schreef op dinsdag 04 februari 2014 @ 23:42:
Even nog ter mogelijke verklaring dan...

Wat je fopen doet is een complete inetconnectie opbouwen, dan gaat je fgetcsv daar alles lezen tot regel x (tot zover niets raars)

Maar wat er dan gebeurt is dat je fopen de data niet cached / streamt dus eigenlijk gewoon een socket openhoud terwijl je niets zit te doen (en dan besluit de server of je fopen commando om de socket maar te sluiten).
Waardoor als fgetcsv de 2e regel probeert te lezen er weer een connectie wordt opgebouwd, alles wordt van regel 1 tot 2 gelezen en regel 2 wordt verwerkt, dan is de connectie weer verbroken en rinse and repeat.

Dus in wezen haal je met 501 regels 501x het betand gedeeltelijk op en wordt er 501x een connectie opgebouwd. En dat kost allemaal tijd, die tijd is niet spannend als het 1x gebeurt, maar als het 501x gebeurt dan gaat die tijd wel tellen...

Zeg dat een http-handshake 10ms kost, dan ben jij dus 5010 ms kwijt aan http-handshakes en dan heb je nog geen regel uit het bestand gelezen. Dat is enkel maar tijd die benodigd is om een regel te gaan lezen.

Download / cache het bestand en je bent eenmalig 10ms kwijt aan een http-handshake, plus dat fgetcsv nu gewoon een pointer in het bestand kan bijhouden ipv dat het bestand elke keer opnieuw gelezen moet worden om een kunstmatige pointer te hanteren.
Hier kan ik gedeeltelijk inkomen en dat was ook wat ik zelf verwachte.

Echter wil het geval dat alle records uit het bestand al verwerkt waren binnen een seconde of 5( ik zag ze immers in de database verschijnen 5 seconden na het starten van het script.) maar vervolgens bleef het script nog ruim 25 seconden draaien op "niks"

Dat is het deel wat ik niet begrijp.

  • las3r
  • Registratie: Augustus 2006
  • Laatst online: 22-11 13:04
Ik zou eerder iets gebruiken i.c.m. file_get_contents, en explode. Leest iets prettiger in mijn optiek, en handelt zelf buffers e.d. af.

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$separator = ",";
$linebreak = "\n";

$csv = file_get_contents("url.csv");
$lines = explode($linebreak, $csv);
foreach ($line in $lines) {
    $cols = explode($separator,$line);
    $dit = $cols[0];
    $dat = $cols[1];
    $zus = $cols[2];
    $zo = $cols[3];
    //  .....
    //  .....
}

?>

  • Pmleader
  • Registratie: Februari 2012
  • Laatst online: 11-11 12:47
las3r schreef op woensdag 05 februari 2014 @ 09:33:
Ik zou eerder iets gebruiken i.c.m. file_get_contents, en explode. Leest iets prettiger in mijn optiek, en handelt zelf buffers e.d. af.

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$separator = ",";
$linebreak = "\n";

$csv = file_get_contents("url.csv");
$lines = explode($linebreak, $csv);
foreach ($line in $lines) {
    $cols = explode($separator,$line);
    $dit = $cols[0];
    $dat = $cols[1];
    $zus = $cols[2];
    $zo = $cols[3];
    //  .....
    //  .....
}

?>
A URL can be used as a filename with this function if the fopen wrappers have been enabled. See fopen() for more details on how to specify the filename. See the Supported Protocols and Wrappers for links to information about what abilities the various wrappers have, notes on their usage, and information on any predefined variables they may provide.
Dat is uit de php.net gebruiksaanwijzing voor file_get_contents dus dan gebruik je nog steeds fopen().
Wat dus deze trage code opleverde.

  • EagleTitan
  • Registratie: Januari 2004
  • Niet online
Pmleader schreef op woensdag 05 februari 2014 @ 11:15:
[...]


[...]


Dat is uit de php.net gebruiksaanwijzing voor file_get_contents dus dan gebruik je nog steeds fopen().
Wat dus deze trage code opleverde.
Niet helemaal waar: je zou nu maar 1x het bestand hoeven openen op de remote host, in plaats van 501x. Dat is waar de trage code vandaan kwam.

  • Gomez12
  • Registratie: Maart 2001
  • Laatst online: 17-10-2023
las3r schreef op woensdag 05 februari 2014 @ 09:33:
Ik zou eerder iets gebruiken i.c.m. file_get_contents, en explode. Leest iets prettiger in mijn optiek, en handelt zelf buffers e.d. af.

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$separator = ",";
$linebreak = "\n";

$csv = file_get_contents("url.csv");
$lines = explode($linebreak, $csv);
foreach ($line in $lines) {
    $cols = explode($separator,$line);
    $dit = $cols[0];
    $dat = $cols[1];
    $zus = $cols[2];
    $zo = $cols[3];
    //  .....
    //  .....
}

?>
1 tip : gebruik gewoon standaard library's / functies en ga niet klooien met explode.
Dit voorbeeld werkt heel mooi voor je test-bestandjes, maar in de praktijk ga je velden tegenkomen met separators in de velden etc. etc.

  • Pmleader
  • Registratie: Februari 2012
  • Laatst online: 11-11 12:47
EagleTitan schreef op woensdag 05 februari 2014 @ 11:20:
[...]


Niet helemaal waar: je zou nu maar 1x het bestand hoeven openen op de remote host, in plaats van 501x. Dat is waar de trage code vandaan kwam.
Zoals ik al eerder schreef:
Echter wil het geval dat alle records uit het bestand al verwerkt waren binnen een seconde of 5( ik zag ze immers in de database verschijnen 5 seconden na het starten van het script.) maar vervolgens bleef het script nog ruim 25 seconden draaien op "niks"
Alle regels uit het bestand waren uitgevoerd binnen een redelijke tijd met de fopen() functie. echter bleef het script nog veel langer draaien op iets anders...
Omdat alle regels wel werden verwerkt zo snel geloof ik nog steeds niet dat het openen van de verbindingen voor de enorme vertraging zorgde.

  • Gomez12
  • Registratie: Maart 2001
  • Laatst online: 17-10-2023
Pmleader schreef op woensdag 05 februari 2014 @ 11:27:
[...]
Omdat alle regels wel werden verwerkt zo snel geloof ik nog steeds niet dat het openen van de verbindingen voor de enorme vertraging zorgde.
Niet geloven maar meten.

Plaats gewoon desnoods om de regel een microtime statement en dump die naar het scherm (of zoek een fatsoenlijke profiler / debugger).

Nu zit je enkel maar wat te gokken net zoals de rest van de mensen hier, alleen jij kan wel de cijfers achterhalen

  • MueR
  • Registratie: Januari 2004
  • Laatst online: 22-11 12:49

MueR

Admin Devschuur® & Discord

is niet lief

las3r schreef op woensdag 05 februari 2014 @ 09:33:
Ik zou eerder iets gebruiken i.c.m. file_get_contents, en explode. Leest iets prettiger in mijn optiek, en handelt zelf buffers e.d. af.
Dit moet je dus vooral niet doen. De native functies zijn altijd sneller en in de meeste gevallen ook robuuster. Ja, in dit geval zou het het probleem wat Gomez12 zo duidelijk uitlegt oplossen, maar jij vervangt het door andere problemen. Als je dit soort operaties op resources op het internet wil doen, dan kopieer je ze naar een tempfile en daar ga je mee werken. Daarnaast is file_get_contents lang niet altijd in staat om een URL te openen, terwijl curl daar juist voor gemaakt is.

[ Voor 8% gewijzigd door MueR op 05-02-2014 16:19 ]

Anyone who gets in between me and my morning coffee should be insecure.

Pagina: 1