Можно ли ускорить рекурсивное сканирование файлов в PHP? - PullRequest
9 голосов
/ 08 марта 2009

Я пытался реплицировать Gnu Find ("find.") В PHP, но кажется невозможным приблизиться к его скорости. Реализации PHP используют как минимум в два раза больше времени поиска. Есть ли более быстрые способы сделать это с помощью PHP?

РЕДАКТИРОВАТЬ: я добавил пример кода с использованием реализации SPL - его производительность равна итеративному подходу

EDIT2: при вызове find из PHP он был на самом деле медленнее, чем собственная реализация PHP. Я думаю, я должен быть доволен тем, что у меня есть:)

// measured to 317% of gnu find's speed when run directly from a shell
function list_recursive($dir) { 
  if ($dh = opendir($dir)) {
    while (false !== ($entry = readdir($dh))) {
      if ($entry == '.' || $entry == '..') continue;

      $path = "$dir/$entry";
      echo "$path\n";
      if (is_dir($path)) list_recursive($path);       
    }
    closedir($d);
  }
}

// measured to 315% of gnu find's speed when run directly from a shell
function list_iterative($from) {
  $dirs = array($from);  
  while (NULL !== ($dir = array_pop($dirs))) {  
    if ($dh = opendir($dir)) {    
      while (false !== ($entry = readdir($dh))) {      
        if ($entry == '.' || $entry == '..') continue;        

        $path = "$dir/$entry";        
        echo "$path\n";        
        if (is_dir($path)) $dirs[] = $path;        
      }      
      closedir($dh);      
    }    
  }  
}

// measured to 315% of gnu find's speed when run directly from a shell
function list_recursivedirectoryiterator($path) {
  $it = new RecursiveDirectoryIterator($path);
  foreach ($it as $file) {
    if ($file->isDot()) continue;

    echo $file->getPathname();
  }
}

// measured to 390% of gnu find's speed when run directly from a shell
function list_gnufind($dir) { 
  $dir = escapeshellcmd($dir);
  $h = popen("/usr/bin/find $dir", "r");
  while ('' != ($s = fread($h, 2048))) {
    echo $s;
  }
  pclose($h);
}

Ответы [ 7 ]

4 голосов
/ 08 марта 2009

Прежде чем начать что-либо менять, запишите свой код .

Используйте что-то вроде Xdebug (плюс kcachegrind для красивого графика), чтобы узнать, где находятся медленные части. Если ты начнешь что-то менять вслепую, ты никуда не денешься.

Мой единственный совет - использовать итераторы каталога SPL, как уже было опубликовано. Позволяя внутреннему коду C делать работу почти всегда быстрее.

4 голосов
/ 08 марта 2009

Я не уверен, что производительность выше, но вы можете использовать рекурсивный итератор каталогов, чтобы сделать ваш код проще ... См. RecursiveDirectoryIterator и 'SplFileInfo` .

$it = new RecursiveDirectoryIterator($from);
foreach ($it as $file)
{
    if ($file->isDot())
        continue;

    echo $file->getPathname();
}
3 голосов
/ 08 марта 2009

PHP просто не может работать так же быстро, как C, просто и понятно.

2 голосов
/ 08 марта 2009

Почему вы ожидаете, что интерпретируемый код PHP будет таким же быстрым, как и скомпилированная C-версия find? Быть только вдвое медленнее, на самом деле очень хорошо.

О единственном совете, который я хотел бы добавить, это сделать ob_start () в начале и ob_get_contents (), ob_end_clean () в конце. Это может ускорить процесс.

1 голос
/ 08 марта 2009

Вы держите N потоков каталогов открытыми, где N - глубина дерева каталогов. Вместо этого попробуйте прочитать записи всего каталога за один раз, а затем переберите записи. По крайней мере, вы максимально увеличите использование кэшей ввода-вывода.

0 голосов
/ 28 октября 2009

Попробуйте использовать scandir(), чтобы прочитать весь каталог сразу, как предложил Джейсон Коэн. Я основал следующий код на коде из комментариев руководства php для scandir()

 function scan( $dir ){
        $dirs = array_diff( scandir( $dir ), Array( ".", ".." ));
        $dir_array = Array();
        foreach( $dirs as $d )
            $dir_array[ $d ] = is_dir($dir."/".$d) ? scan( $dir."/".$d) : print $dir."/".$d."\n";
 }
0 голосов
/ 08 марта 2009

Возможно, вы захотите серьезно подумать только об использовании GNU find. Если он доступен, а безопасный режим не включен, вам, вероятно, понравятся результаты:

function list_recursive($dir) { 
  $dir=escapeshellcmd($dir);
  $h = popen("/usr/bin/find $dir -type f", "r")
  while ($s = fgets($h,1024)) { 
    echo $s;
  }
  pclose($h);
}

Однако, возможно, существует какой-то каталог, который настолько велик, что вам не захочется и беспокоиться об этом. Рассмотрим амортизацию медлительности другими способами. Ваша вторая попытка может быть проверена (например) путем простого сохранения стека каталогов в сеансе. Если вы предоставляете пользователю список файлов, просто соберите страничку и сохраните оставшуюся часть состояния в сеансе для страницы 2.

...