Можно ли ускорить рекурсивное сканирование файлов в PHP?
Я пытался реплицировать 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 ответов
PHP просто не может работать так же быстро, как C, просто и понятно.
Я не уверен, что производительность выше, но вы могли бы использовать рекурсивный итератор каталогов, чтобы сделать ваш код проще... RecursiveDirectoryIterator
и 'SplFileInfo`.
$it = new RecursiveDirectoryIterator($from);
foreach ($it as $file)
{
if ($file->isDot())
continue;
echo $file->getPathname();
}
Прежде чем начать что-либо менять, профилируйте свой код.
Используйте что-то вроде Xdebug (плюс kcachegrind для симпатичного графика), чтобы узнать, где медленные части. Если ты начнешь что-то менять вслепую, ты никуда не денешься.
Единственный мой совет - использовать итераторы каталога SPL, как уже написано. Позволяя внутреннему коду C делать работу почти всегда быстрее.
Почему вы ожидаете, что интерпретируемый код PHP будет таким же быстрым, как и скомпилированная C-версия find? Быть только в два раза медленнее на самом деле очень хорошо.
О единственном совете, который я бы добавил, это сделать ob_start() в начале и ob_get_contents(), ob_end_clean() в конце. Это может ускорить процесс.
Вы держите N потоков каталогов открытыми, где N - глубина дерева каталогов. Вместо этого попробуйте прочитать записи всего каталога за один раз, а затем переберите записи. По крайней мере, вы максимально увеличите использование кэшей ввода-вывода.
Попробуйте использовать 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";
}
Вы можете серьезно подумать только об использовании 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.