Marc2: Code verbessern?

Hallo,
ich habe mir in PHP folgenden Code geschrieben.

Die Aufgabe dieses Codes ist es aus einer txt-Datei (9 MB Groß) alle Wörter rauszusuchen die in das Suchmuster passen. In etwa sind dies 140 000 Wörter. Leider musste ich feststellen, dass PHP daran ganz schön zu schaffen hat. Es Arbeitet nun schon seit 4 Stunden und es ist noch kein Ende in Sicht. Der Code funktioniert aber. Es kommen laufend neue Datenbankeinträge hinzu.

Meine Frage an euch. Lässt sich mein Code irgendwie verbessern, dass PHP schneller arbeiten kann?

  
<?php  
set_time_limit(0);  
mysql_connect("localhost", " ", " ") or die("Verbindung zu MySQL gescheitert!");  
mysql_select_db(" ") or die("Datenbankzugriff gescheitert!");  
  
$woerter = file("woerter.txt");  
  
for ($i = 0; $i <= count($woerter); $i++){  
 preg_match_all('/(?<anfang>^\w+) {|(?<mitte> [\w]+) {/', $woerter[$i], $treffer);  
  
 if ($treffer['anfang'][0] != "") {  
  $sql = "SELECT wort FROM woerter WHERE wort = '".$treffer['anfang'][0]."'";  
  $result = mysql_query($sql);  
  if (mysql_num_rows($result) == 0) {  
   $sql = "INSERT INTO woerter VALUES('', '".$treffer['anfang'][0]."', '', '', now())";  
   mysql_query($sql);  
  }  
 }  
  
 for ($z = 1; $z <= 6; $z++) {  
  if ($treffer['mitte'][$z] != "") {  
   $sql = "SELECT wort FROM woerter WHERE wort = '".$treffer['mitte'][$z]."'";  
   $result = mysql_query($sql);  
   if (mysql_num_rows($result) == 0) {  
     $sql = "INSERT INTO woerter VALUES('', '".$treffer['mitte'][$z]."', '', '', now())";  
     mysql_query($sql);  
   }  
  }  
 }  
  
}  
?>  

  1. Hallo

    Meine Frage an euch. Lässt sich mein Code irgendwie verbessern,

    auf jeden Fall. Code ohne Kommentar ist eh' miserabler Code.

    dass PHP schneller arbeiten kann?

    for ($i = 0; $i <= count($woerter); $i++){

    es gibt foreach

    Setze einen eindeutigen Index auf die Spalte wort - und füge einfach ein, ohne vorher zu überprüfen. Rechne damit, dass das INSERT-Statement fehlschlagen kann (wegen Indexverletzung), dann war das Wort bereits enthalten. Das erspart Dir schon einmal einen großen Teil Deiner SQL-Anweisungen.

    Ich habe mir _nicht_ versucht, nachzuvollziehen, was Dein Code tun soll. Das ist _Deine_ Aufgabe, es dem potentiellen Helfer und _Dir_ selbst zu erklären. Kommentiere Deinen Code!

    Freundliche Grüße

    Vinzenz

    1. Setze einen eindeutigen Index auf die Spalte wort - und füge einfach ein, ohne vorher zu überprüfen. Rechne damit, dass das INSERT-Statement fehlschlagen kann (wegen Indexverletzung), dann war das Wort bereits enthalten. Das erspart Dir schon einmal einen großen Teil Deiner SQL-Anweisungen.

      Gute Idee, allerdings funktioniert es nicht. Ich verstehe nicht warum.
      Die Spalte wort habe ich als UNIQUE gekennzeichnet. Will ich nun über Phpmyadmin zwei mal das Wort Baum einfügen zeigt er treu eine Fehlermeldung an. Aus meiner txt Datei wird allerdings weiterhin alles doppelt und dreifach eingetragen.

      Beispielsweise trägt PHP bei diesem Text zwei mal das Wort Aalspies ein, obwohl es doch nur einmal gehen darf?

      Aalspies {m} | Aalspies {m} :: eel spear | eel spears
      Aalsuppe {f} [cook.] | Aalsuppen {pl} :: eel soup | eel soups

      Hat von euch jemand eine Erklärung dafür?

      1. Fehler gefunden. Da hat sich doch tatsächlich ein Leerzeichen mit eingeschmuggelt :)

        1. Wow nur 5 Minuten und alle Einträge sind fertig.^^

    2. Hallo,

      for ($i = 0; $i <= count($woerter); $i++){
      es gibt foreach

      for ist schneller als foreach. Allerdings ist ebenso anzumerken, dass PHP so "programmiert" ist, dass es mit jedem Schleifendurchlauf auch angegebene Funktionsaufrufe wiederholt. Besser ist daher:

        
      $c=count($woerter);  
      for($i=0;$i<$c;$i++){}  
      
      

      Gruß aus Berlin!
      eddi

      --
      Hypochondrie vor Parodontitis? Nikotin beruhigt die Nerven davon!
      1. echo $begrüßung;

        for ist schneller als foreach.

        Wie groß ist der Unterschied? Ist er signifikant spürbar?

        echo "$verabschiedung $name";

        1. Hallo,

          for ist schneller als foreach.

          Wie groß ist der Unterschied? Ist er signifikant spürbar?

          ca 10%
          Steht ein Rechner kurz vor der Überlastung, ist es sicher signifikant spürbar. ;)

          Gruß aus Berlin!
          eddi

          --
          *heul* Man, meine schönen Korinthen. :(

      2. Hello,

        for ist schneller als foreach.

        Das kann ich gar nicht glauben, da bei For jedesmal die gesamte Liste wieder von vorne gelesen werden muss, bis das passende Element gefunden wurde, während bei foreach gleich das nächste in der Liste benutzt werden kann. Es muss ja kein Index überprüft werden...

        Bei Verwendung von For müsste der Ordnung halber dem Zugriff auf das Lement auch immer noch ein isset() vorgeschaltet werden. Es könnte ja eines fehlen.

        Ich schaue mir das aber bestimmt nochmal im Quellcode an. Mal sehen, ob ich da soweit durchsteige.

        Ein harzliches Glückauf

        Tom vom Berg

        --
        Nur selber lernen macht schlau
        http://bergpost.annerschbarrich.de
        1. Hallo Tom,

          for ist schneller als foreach.

          Das kann ich gar nicht glauben,...

          Ich schaue mir das aber bestimmt nochmal im Quellcode an. Mal sehen, ob ich da soweit durchsteige.

          hier noch was zum durchsteigen von damals.

          Gruß aus Berlin!
          eddi

          --
          Hypochondrie vor Parodontitis? Nikotin beruhigt die Nerven davon!
        2. Tach.

          for ist schneller als foreach.

          Das kann ich gar nicht glauben ...

          Ich auch nicht. Selbst wenn ich das in PHP5 so teste wie eddi damals, liegt foreach vorne. Im *normalen* Betrieb habe ich noch nie wirklich Unterschiede zwischen beiden bemerkt. Viel schwerer wiegt da der bereits erwähnte Mehrfachaufruf von count().

          ... da bei For jedesmal die gesamte Liste wieder von vorne gelesen werden muss, bis das passende Element gefunden wurde

          Nö. Für numerische Arrayschlüssel schmeißt PHP noch nicht mal seine Hashfunktion an. Von Gehangel durch verlinkte Listen ganz zu schweigen ...

          --
          Once is a mistake, twice is Jazz.
          1. Hello,

            ... da bei For jedesmal die gesamte Liste wieder von vorne gelesen werden muss, bis das passende Element gefunden wurde

            Nö. Für numerische Arrayschlüssel schmeißt PHP noch nicht mal seine Hashfunktion an. Von Gehangel durch verlinkte Listen ganz zu schweigen ...

            Ja und? Wann kommt Deine Version von der Auflösung des Knotens?
            Wird die Liste vorher intern sortiert? Das wäre eine intelligente Implementation!

            Ein harzliches Glückauf

            Tom vom Berg

            --
            Nur selber lernen macht schlau
            http://bergpost.annerschbarrich.de
            1. Tach.

              ... da bei For jedesmal die gesamte Liste wieder von vorne gelesen werden muss, bis das passende Element gefunden wurde

              Nö. Für numerische Arrayschlüssel schmeißt PHP noch nicht mal seine Hashfunktion an. Von Gehangel durch verlinkte Listen ganz zu schweigen ...

              Ja und? Wann kommt Deine Version von der Auflösung des Knotens?
              Wird die Liste vorher intern sortiert? Das wäre eine intelligente Implementation!

              Als ich das letzte Mal nachgeschaut habe, wurden Arrays in PHP intern über Hash Tables realisiert. Damit ist es unnötig, auf der Suche nach einem bestimmten Element eine verkettete Liste schrittweise zu durchlaufen. *Zusätzlich* sind die Elemente als doppelt verkettete Liste ... ähm, verkettet.

              --
              Once is a mistake, twice is Jazz.
          2. Huhu,

            Selbst wenn ich das in PHP5 so teste wie eddi damals, liegt foreach vorne.

            welche virtual maschine nutzt Du? (Option --with-zend-vm des configure-Scripts)

            Gruß aus Berlin!
            eddi

            --
            Hypochondrie vor Parodontitis? Nikotin beruhigt die Nerven davon!
            1. Tach.

              Selbst wenn ich das in PHP5 so teste wie eddi damals, liegt foreach vorne.

              welche virtual maschine nutzt Du? (Option --with-zend-vm des configure-Scripts)

              Die Standardeinstellung für PHP 5.2.5, was also CALL sein müßte, wenn ich das richtig sehe.

              --
              Once is a mistake, twice is Jazz.
  2. Hi,

    du könntest den Code sicherlich schneller machen, wenn du nicht für jedes des 140.000 Wörter eine einzelne Datenbankabfrage machen würdest.

    Mein Vorschlag: Lege eine Tabelle an, die du nur für den Import verwendest und vorher sowie nachher leerst / löschst. Für die Tabelle reicht ja ein Feld für "Wort" mit einem Primären eindeutigen Schlüssel/Index darauf. Dann fügst du erstmal in deinen Schleifen alle Wörte mithilfe von INSERT INTO temp_woerter VALUES ($phpwortwert) ein. Wenn es da doppelte gibt, bekommst du von mysql eine Fehlermeldung wegen Schlüsselverletzung.

    Wenn alle Wörter dann drin sind, kannst du (sicherlich) mit einem [code lang=sql]INSERT INTO ... SELECT .... FROM temp_woerter t1 LEFT JOIN woerter t2 ON t1.wort = t2.wort WHERE t2.wort IS NULL[/sql] alle importierten Wörter, die noch nicht in der finalen Tabelle sind in einem Rutsch importieren.

    Auf die Tabelle 'woerter' würde ich noch einen Index auf die Spalte 'Wort' packen, vielleicht sogar einen eindeutigen (UNIQUE), denn die Verwendung von dieser Spalte macht den Eindruck als wäre sie eindeutig.

    Vielleicht hilft auch schon einfach nur der Index :)

    Ciao, Frank

  3. echo $begrüßung;

    Auch wenn du es schon gelöst hast, noch ein paar Tipps.

    Lässt sich mein Code irgendwie verbessern, dass PHP schneller arbeiten kann?

    Vor der Verbesserung steht erst einmal eine Analyse des bisherigen Zustands. Wo liegen die Stellen, die zu viel Zeit beanspruchen? Wird das Script mit zunehmender Datenmenge immer langsamer?

    Letzteres lässt sich mit Hilfe von Kontrollausgaben ermitteln. Beispielsweise pro Durchlauf einen Punkt ausgeben (flush() nicht vergessen, damit der Server die Ausgabe sofort an den Client sendet und nicht erst auf eine bestimmte Paketgröße wartet), oder bei vielen Daten alle x Durchläufe ein Lebenszeichen senden. So kann man den Fortschritt verfolgen und ein Ende abschätzen.

    Dauert jeder Durchlauf gleich lang, untersucht man nun, welcher Codeteil welche Zeit verbraucht. Hat man keinen Profiler zur Hand kann man mit microtime() die Zeit ermitteln und dann Differenzen ausrechnen.

    Wenn Datenbankabfragen zu lange dauern, liegt das meist am fehlenden oder nicht genutzten Index. Ein EXPLAIN vor der Abfrage gibt darüber Auskunft.

    Soweit allgemeine Vorgehensweisen.

    mysql_connect("localhost", " ", " ") or die("Verbindung zu MySQL gescheitert!");
    mysql_select_db(" ") or die("Datenbankzugriff gescheitert!");

    die() ist meistens keine angemessene Fehlerbehandlung. Da das Script anscheinend nur ein einziges Mal laufen soll, ist es hier zwar vertretbar, aber dessen Ausgabe unzweckmäßig. Gib doch, wenn einfach nur mysql_error() aus. Das erzählt dir deutlich genauer als dein Text, was losgewesen ist.

    $sql = "SELECT wort FROM woerter WHERE wort = '".$treffer['anfang'][0]."'";
      $result = mysql_query($sql);
      if (mysql_num_rows($result) == 0) {

    Wo ist deine Fehlerbehandlung abgeblieben? Auch mysql_query() kann fehlschlagen. Außerdem fehlt die Behandlung der Daten für den MySQL-Statement-Kontext. Wenn in deiner Liste O'Brien steht, erzeugt das einen Syntaxfehler. (Stichwort: mysql_real_escape_string())

    Desweiteren ist zum reinen Zwecke der Zählung ein COUNT(*) angebrachter. Wenn mysql_num_rows() die Ergebnismenge zählen soll, muss diese zunächst vollständig vom Server abgeholt sein. mysql_query() macht das von sich aus bei jeder Abfrage im Hintergrund, eben auch damit mysql_num_rows() funktionieren kann. Du nutzt anschließend die abgefragte Datenmenge überhaupt nicht. Das COUNT()-Ergebnis ist nur ein Datensatz, im Gegensatz zu möglicherweise vielen überflüssigen.

    Auch das ist nur eine allgemeine Vorgehensweise. In deinem Fall gibt es ja mit dem Unique-Index die deutlich bessere Lösung als das Zählen.

    $sql = "INSERT INTO woerter VALUES('', '".$treffer['anfang'][0]."', '', '', now())";

    Hier fehlt wieder die kontextgerechte Behandlung der Eingabedaten.

    Wenn das gleiche Statement mit unterschiedlichen Daten immer wieder ausgeführt werden soll, spielen Prepared Statements ihren Vorteil aus. Nicht nur, dass du dir bei ihrer Verwendung das Maskieren der Daten sparen kannst, es fällt auch noch das ständige Parsen des Statements am Server weg. Um Prepared Statements nutzen zu können, musst du aber auf die modernere mysqli-Extension PHPs umsteigen.

    echo "$verabschiedung $name";