Baba: time killer is_file()

Liebe Helfer,

seit einiger Zeit ist mir aufgefallen, dass der erste Seitenaufbau innerhalb einer Session sehr lange dauert. 14 Sekunden. Ich ging auf Spurensuche und fand heraus, dass es mit einer Funktion LoadDirectory zu tun hat. Dort lese ich ein Verzeichnis aus, welches ca 1000 (<10kb) Dateien enthält. Die Funktion gibt ein Array zurück. Man kann verschiedene Parameter der Funktion übergeben. Zum Beispiel "Suchmuster", "Nur-Dateien", etc... (Funktion siehe unten).

Mir kam es dann aber doch etwas krass vor, dass 1000 Dateien 14 sek. brauchen sollten (nur Dateinamen zurückgeben, nicht öffnen). Also habe ich mal in die while-Schleife reingeschaut. Endlich habe ich eine Zeile identifiziert, die die lange Zeit verursacht.

// this is very very time consuming:  
if(($onlyfiles) and (!is_file($path.$file)))                        $stat = false;

Mit Auskommentieren dieser Zeile geht es auch beim ersetn Seitenaufbau in weniger als 1 sek.

Übrigens geht es ab dem zweiten Seitenaufbau immer schnell, auch mit dieser Zeile. Ich nehme an, von da an cached php die Ordnerstruktur.

Die Frage: kann es wirklich sein, dass die Funktion is_file() so viel Zeit in Anspruch nimmt? Was kann ich stattdessen für diesen Schalter (nur Dateien zurückgeben) einbauen?

Vielen Dank

Hier kommt die Funktion:

function LoadDirectory($path, $pattern = null, $ausschluss = null, $onlyfiles = true){  
  
  // to store processing time  
  $lap = array();  
  
  if(substr($path, -1) != "/") $path.="/";  
  
  if(is_dir($path)){  
  
    $all_files = array();  
  
    if(is_null($ausschluss)) $ausschluss = array();  
    $ausschluss[] = ".";  
    $ausschluss[] = "..";  
    $ausschluss[] = ".htaccess";  
    $ausschluss[] = ".ftpquota";  
  
    $handle    = opendir($path);  
  
    if(is_null($pattern))   $pattern = array();  
    if(!is_array($pattern)) $pattern = array($pattern);  
  
    $lap["start"] = microtime(true);  
    while (false !== ($file = readdir($handle))){  
  
      $stat = true;  
      foreach($pattern as $filter) if(!is_integer(strpos($file,$filter))) $stat = false;  
      if(in_array($file,$ausschluss))                                     $stat = false;  
  
      // this is very very time consuming:  
      if(($onlyfiles) and (!is_file($path.$file)))                        $stat = false;  
  
      if($stat) $all_files[] = $file;  
  
    }  
    $lap["end"] = microtime(true);  
  
    if (count($all_files)!=0) sort($all_files);  
  
  
  
    foreach($lap as $i=>$time) d(round($time-$lap["start"],2), "LAP ---- load dir ---- $path ---- $i");  
  
  
  
    return $all_files;  
  
   }  
  
   return false;  
  
}  
  
// d() is an alias of var_dump()  
  
function d($x, $y=null){  
  
  echo "<pre><div style='margin:0px; line-height:12px; width:100%; background-color:#515151; height:12px; color:#ffffff; font-size:10px;'>$y</div><div style='min-height:20px; background-color:#ffffff;'>";  
  var_dump($x);  
  echo "</div></pre>";  
}

Cheers,
Baba

  1. Der zweite Aufruf kann übrigens schneller sein, weil das Betriebssystem - also nicht PHP - die Directory-Einträge zwischenspeichert. Womöglich könnte es also hilfreich sein, wenn Du ein anderes Dateisystem oder beim mounten andere Optionen benutzt, damit das OS mit vielen Einträgen in einem Verzeichnis besser zu recht kommt.

    Falles wider Erwarten tatsächlich an is_file() liegen sollte:

    der Backtick-Operator könnte Dir WOMÖGLICH helfen. Allerdings habe ich Zweifel. Am besten, Du misst mit den Unterschied selbst.

    <?php
    $dir=/foo/bar/';
    $output = ls -al $dir;
    echo "<pre>$output</pre>";
    ?>

    Splitte die Rückgaben erst zeilenweise - steht dann als erstes Zeichen ein '-' im String ist es eine normale Datei (und interessiert Dich), steht da ein "d" ist es ein Verzeichnis.

    Splitte die Ausgaben danach an den(Plural!) Leerzeichen. Bitte beachte, das ls ein Befehl ist, der unterschiedlich implementiert sein kann. Mit der Vorgehensweise verlierst Du also die Unabhängigkeit vom Betriebssytem. Unter Windows wird das also schon mal nicht so gehen.

    1. Unter Windows wird das also schon mal nicht so gehen.

      ...womit es leider ausfällt. Hier zum testen und später auf dem X-Server läuft es auf Windows.

      Habe übrigens gefunden, dass is_file() wirklich langsam sein soll, auch wenn ich das an diesen Zahlen nicht erkennen kann.

      Ich könnte es unter Windows auch mit

      $output = `dir /A-D`;  
      d($output);
      

      versuchen, weiß allerdings nicht, ob ich Lust habe diesen String irgendwie so auseinanderzubasteln, dass ich da die Dateien rausfiltern kann. Schneller wäre es dann bestimmt.

      Frage wäre aber noch einmal an alle: ist das realistisch: bei 1000 Dateien nach is_file() gefragt == 14 sek?

      Nutze absolute Pfade, habe gelesen, dass das langsamer sein soll. Relative gehen aber nicht wirklich.

      Cheers,
      Baba

      1. Hier zum testen und später auf dem X-Server läuft es auf Windows.

        Frage wäre aber noch einmal an alle: ist das realistisch: bei 1000 Dateien nach is_file() gefragt == 14 sek?

        Ich glaube, PHP wird primär für unixoide OS entwickelt und dann portiert. Um Performancefragen einzelner Funktionen unter Windows macht man sich wohl dabei (getreu dem Motto "Wer macht denn sowas?") seltener Gedanken. Ich halte den Wert unter Windows, insbesondere bei einem ohnehin belasteten Dateisystem, insoweit durchaus für realistisch.

        Du wirst also eventuell nicht darum herum kommen, ggf. die Ausgaben des Befehls dir /A-d zu verarbeiten.

        Das ist übrigens auch trivial.

        fred

        1. Das ist übrigens auch trivial.

          Naja.

          C:\Documents and Settings\geheim>dir /A-d
           Volume in drive C is OS
           Volume Serial Number is 00B7-969E

          Directory of C:\Documents and Settings\geheim

          20.06.2012  12:38             4.120 .recently-used.xbel
          27.06.2012  17:04         7.864.320 NTUSER.DAT
          28.06.2012  15:32             1.024 ntuser.dat.LOG
          10.04.2012  15:47               178 ntuser.ini
          11.06.2012  14:46           132.295 rect5743.png
          11.06.2012  14:49           145.592 rect5755.png
                         6 File(s)      8.147.529 bytes
                         0 Dir(s)  220.116.467.712 bytes free

          C:\Documents and Settings\geheim>

          Kann ich darauf bauen, dass die Anzahl an lines über und unter der Tabelle immer die gleiche ist? Dann könnte ich sie abschneiden, dann von rechts jede Zeile lesen bis Leerzeichen. Irgendwas in der Richtung strrpos und " ",...

          Vielen Dank aber für die Anregung!

          Cheers,
          Baba

          1. Hallo,

            C:\Documents and Settings\geheim>dir /A-d

            Kann ich darauf bauen, dass die Anzahl an lines über und unter der Tabelle immer die gleiche ist? Dann könnte ich sie abschneiden, dann von rechts jede Zeile lesen bis Leerzeichen. Irgendwas in der Richtung strrpos und " ",...

            einfach noch zusätzlich /B verwenden :-)
            siehe:

            dir /?

            Freundliche Grüße

            Vinzenz

      2. Hallo,

        Ich könnte es unter Windows auch mit

        $output = dir /A-D;

        d($output);

        
        >   
        > versuchen, weiß allerdings nicht, ob ich Lust habe diesen String irgendwie so auseinanderzubasteln, dass ich da die Dateien rausfiltern kann.  
          
        na ich weiß nicht, dann würde ich doch lieber mit glob() nachsehen, welche Dateien im Verzeichnis existieren. Da bekomme ich das Ergebnis in leicht verarbeitbarer Form, und vermutlich ist das auch schneller, als erst noch eine Shell als Kind-Prozess abzuspalten.  
          
        
        > Frage wäre aber noch einmal an alle: ist das realistisch: bei 1000 Dateien nach is\_file() gefragt == 14 sek?  
          
        Also rund 14ms pro Datei-Lookup. Klingt gar nicht so schlecht, finde ich.  
          
        Ciao,  
         Martin  
        
        -- 
        Chef:         Zum vierten Mal in dieser Woche erwische ich Sie nun schon beim Zuspätkommen. Was haben Sie dazu zu sagen?  
        Angestellter: Dann muss heute Donnerstag sein.  
        Selfcode: fo:) ch:{ rl:| br:< n4:( ie:| mo:| va:) de:] zu:) fl:{ ss:) ls:µ js:(
        
        1. na ich weiß nicht, dann würde ich doch lieber mit glob() nachsehen, welche Dateien im Verzeichnis existieren. Da bekomme ich das Ergebnis in leicht verarbeitbarer Form, und vermutlich ist das auch schneller, als erst noch eine Shell als Kind-Prozess abzuspalten.

          laut dem verlinkten Performancetest ist glob() allerdings nicht schneller. Ich werde es aber dennoch mal versuchen. Für diesen Fall habe ich allerdings den Schalter $onlyfiles mal auf false gesetzt, da ich in dem Ordner nur Files erwarten darf.

          Also rund 14ms pro Datei-Lookup. Klingt gar nicht so schlecht, finde ich.

          So gesehen ist das nicht zu langsam. Danke für diese einfache Multiplikation :)

          Dank auch an Tom. Hat mir, wie immer, sehr geholfen alles.

          Cheers,
          Baba

          1. Hi,

            na ich weiß nicht, dann würde ich doch lieber mit glob() nachsehen, welche Dateien im Verzeichnis existieren. Da bekomme ich das Ergebnis in leicht verarbeitbarer Form, und vermutlich ist das auch schneller, als erst noch eine Shell als Kind-Prozess abzuspalten.
            laut dem verlinkten Performancetest ist glob() allerdings nicht schneller.

            okay, vielleicht nicht schneller. Das war auch nur eine Vermutung meinerseits.
            Zumindest aber einfacher zu handhaben. Und plattformunabhängig.

            Übrigens gibt es für "dir" auch einen Schalter, der alles außer den Dateinamen selbst in der Ausgabe weglässt. Dann hast du nur noch eine reine Liste von Dateinamen, einer pro Zeile. Wenn ich mich nicht irre, ist das /l.

            Also rund 14ms pro Datei-Lookup. Klingt gar nicht so schlecht, finde ich.
            So gesehen ist das nicht zu langsam. Danke für diese einfache Multiplikation :)

            Eingesprungener Dreisatz! :-)

            Ciao,
             Martin

            --
            Realität ist eine Illusion, die durch Unterversorgung des Körpers mit Alkohol entstehen kann.
            Selfcode: fo:) ch:{ rl:| br:< n4:( ie:| mo:| va:) de:] zu:) fl:{ ss:) ls:µ js:(
          2. Hello,

            laut dem verlinkten Performancetest ist glob() allerdings nicht schneller. Ich werde es aber dennoch mal versuchen. Für diesen Fall habe ich allerdings den Schalter $onlyfiles mal auf false gesetzt, da ich in dem Ordner nur Files erwarten darf.

            Hast Du auch die Methode versucht,

              
                $_filenames = glob($muster,GLOB_MARK);  
              
                foreach($_filenames as $key => $name)  
                {  
                    if(substr($name,-1) == DIRECTORY_SEPARATOR)  
                    {  
                        unset($_filenames[$key];  
                    }  
                }  
              
                echo "<pre>\r\n">;  
                echo htmlspecialchars(print_r($_filenames,1));  
                echo "</pre>\r\n">;  
            
            

            und die Zeit gemessen und verglichen mit der, die deine alte Methode benötigt?

            Ich habe leider kein Verzeichnis mit 1000 Dateien, nur mit 150.000. Da kann ich das jetzt vergleichen... Und extra eins anlegen wollte ich jetzt nicht.

            Liebe Grüße aus dem schönen Oberharz

            Tom vom Berg

            --
             ☻_
            /▌
            / \ Nur selber lernen macht schlau
            http://bergpost.annerschbarrich.de
  2. Hello,

    Mir kam es dann aber doch etwas krass vor, dass 1000 Dateien 14 sek. brauchen sollten (nur Dateinamen zurückgeben, nicht öffnen). Also habe ich mal in die while-Schleife reingeschaut. Endlich habe ich eine Zeile identifiziert, die die lange Zeit verursacht.

    Fasrt die ganze Funktion kannst Du vermutlich ganz leicht gegen

    glob() http://de3.php.net/manual/en/function.glob.php

    ersetzen. Welche Features benötigst Du denn wirklich?

    Die Funktion is_file() benötigt vermutlich die viele Zeit. Darauf wirst Du aber nicht verzichten können, wenn wirklich nur Files und bei Linux damit auch Links auf Files gefunden werden sollen.

    Liebe Grüße aus dem schönen Oberharz

    Tom vom Berg

    --
     ☻_
    /▌
    / \ Nur selber lernen macht schlau
    http://bergpost.annerschbarrich.de
    1. Hello,

      Fast die ganze Funktion kannst Du vermutlich ganz leicht gegen

      glob() http://de3.php.net/manual/en/function.glob.php

      ersetzen. Welche Features benötigst Du denn wirklich?

      Die Funktion is_file() benötigt vermutlich die viele Zeit. Darauf wirst Du aber nicht verzichten können, wenn wirklich nur Files und bei Linux damit auch Links auf Files gefunden werden sollen.

      Du könntest mal überlegen, wie Du das "only_files" durch die Hintertür erreichen kannst.

      Versuch es doch mal mit glob($muster, GLOB_MARK) und lasse dann in der Ergebnismenge im Array alle Einträge mit unset löschen, die am Ende einen Slash stehen haben.

      Dafür muss dann nicht jedes Mal wieder eine Verbindung zum Filesystem aufgebaut werden.
      Durch das Muster kannst Du sowieso schon . und .. ausschließen.

      Liebe Grüße aus dem schönen Oberharz

      Tom vom Berg

      --
       ☻_
      /▌
      / \ Nur selber lernen macht schlau
      http://bergpost.annerschbarrich.de