файл exists () слишком медленный в PHP. Кто может предложить более быструю альтернативу?

при отображении изображений на нашем веб-сайте мы проверяем, существует ли файл с вызовом file_exists(). Мы возвращаемся к фиктивному изображению, если файл отсутствовал.

однако профилирование показало, что это самая медленная часть генерации наших страниц с file_exists() взяв 1/2 МС в файл. Мы тестируем только 40 или около того файлов, но это все еще толкает 20 мс на время загрузки страницы.

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

19 ответов


file_exists() должно быть очень недорогой операцией. Заметьте также, что file_exists строит собственный кэш, чтобы помочь с производительностью.

см.:http://php.net/manual/en/function.file-exists.php


использовать абсолютные пути! в зависимости от вашего include_path настройка PHP проверяет все (!) эти dirs, если вы проверяете относительные пути к файлам! Вы можете сбросить include_path временно перед проверкой существования.

realpath() делает то же самое, но я не знаю, если это быстрее.

но доступ к файлу I / O всегда медленный. Жесткий диск IS медленнее, чем вычисление чего-то в процессоре, обычно.


самый быстрый способ проверить наличие локального файла -stream_resolve_include_path ():

if (false !== stream_resolve_include_path($s3url)) { 
  //do stuff 
}

результаты stream_resolve_include_path () vs file_exists ():

Test name       Repeats         Result          Performance     
stream_resolve  10000           0.051710 sec    +0.00%
file_exists     10000           0.067452 sec    -30.44%

в тесте используются абсолютные пути. Тестовый источник здесь. Версия PHP:

PHP 5.4.23-1~dotdeb.1 (cli) (построенный: Dec 13 2013 21:53:21)
Авторское право (c) 1997-2013 PHP Группа
Zend Engine v2.4.0, Copyright (c) 1998-2013 Zend Technologies


мы возвращаемся к фиктивному изображению, если файл отсутствует

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

таким образом, у вас будет немного накладных расходов на перенаправление и не заметная задержка на стороне клиента. По крайней мере, вы избавитесь от "дорогого" (которого, я знаю, нет) звонка file_exists.

просто мысли.


бенчмарки с PHP 5.6:

Существующий Файл:

0.0012969970 : stream_resolve_include_path + include  
0.0013520717 : file_exists + include  
0.0013728141 : @include  

Недопустимый Файл:

0.0000281333 : file_exists + include  
0.0000319480 : stream_resolve_include_path + include  
0.0001471042 : @include  

Недействительным Папку:

0.0000281333 : file_exists + include  
0.0000360012 : stream_resolve_include_path + include  
0.0001239776 : @include  

код:

// microtime(true) is less accurate.
function microtime_as_num($microtime){
  $time = array_sum(explode(' ', $microtime));
  return $time;
}

function test_error_suppression_include ($file) {
  $x = 0;
  $x = @include($file);
  return $x;
}

function test_file_exists_include($file) {
  $x = 0;
  $x = file_exists($file);
  if ($x === true) {
    include $file;
  }
  return $x;
}

function test_stream_resolve_include_path_include($file) {
  $x = 0;
  $x = stream_resolve_include_path($file);
  if ($x !== false) {
    include $file;
  }
  return $x;
}

function run_test($file, $test_name) {
  echo $test_name . ":\n";
  echo str_repeat('=',strlen($test_name) + 1) . "\n";

  $results = array();
  $dec = 10000000000; // digit precision as a multiplier

  $i = 0;
  $j = 0;
  $time_start = 0;
  $time_end = 0;
  $x = -1;
  $time = 0;

  $time_start = microtime();
  $x= test_error_suppression_include($file);
  $time_end = microtime();
  $time = microtime_as_num($time_end) - microtime_as_num($time_start);

  $results[$time*$dec] = '@include';

  $i = 0;
  $j = 0;
  $time_start = 0;
  $time_end = 0;
  $x = -1;
  $time = 0;

  $time_start = microtime();
  $x= test_stream_resolve_include_path_include($file);
  $time_end = microtime();
  $time = microtime_as_num($time_end) - microtime_as_num($time_start);

  $results[$time * $dec] = 'stream_resolve_include_path + include';

  $i = 0;
  $j = 0;
  $time_start = 0;
  $time_end = 0;
  $x = -1;
  $time = 0;

  $time_start = microtime();
  $x= test_file_exists_include($file);
  $time_end = microtime();
  $time = microtime_as_num($time_end) - microtime_as_num($time_start);

  $results[$time * $dec ] = 'file_exists + include';

  ksort($results, SORT_NUMERIC);

  foreach($results as $seconds => $test) {
    echo number_format($seconds/$dec,10) . ' : ' . $test . "\n";
  }
  echo "\n\n";
}

run_test($argv[1],$argv[2]);

выполнение командной строки:

php test.php '/path/to/existing_but_empty_file.php' 'Existing File'  
php test.php '/path/to/non_existing_file.php' 'Invalid File'  
php test.php '/path/invalid/non_existing_file.php' 'Invalid Folder'  

file_exists() автоматически кэшируется PHP. Я не думаю, что вы найдете более быструю функцию в PHP, чтобы проверить существование файла.

посмотреть этой теме.


создайте процедуру хэширования для разделения файлов на несколько подкаталогов.

именем.jpg -> 012345 -> /01/23/45.формат JPG

кроме того, вы можете использовать mod_rewrite для возврата изображения-заполнителя для запросов в каталог изображений, который 404.


Я не знаю, что вы хотите сделать, но вы могли бы просто пусть клиент справится с этим.


если вы проверяете только существующие files используйте is_file(). file_exists() проверяет наличие существующего файла или каталога, поэтому, возможно,is_file() может быть немного быстрее.


Они все в одном каталоге? Если так мая стоит получить список файлов и хранить их в хэше и сравнивать с этим, а не со всеми поисками file_exists.


Если вы хотите проверить наличие файла изображения, a быстрее способ применения getimagesize !

быстрее локально и удаленно!

if(!@GetImageSize($image_path_or_url)) // False means no imagefile
 {
 // Do something
 }

Я нахожу 1 / 2ms за вызов очень, очень доступным. Я не думаю, что есть намного более быстрые альтернативы, так как файловые функции очень близки к нижним слоям, которые обрабатывают файловые операции.

однако вы можете написать оболочку в file_exists (), которая кэширует результаты в memcache или аналогичный объект. Это должно сократить время почти до нуля в повседневном использовании.


вы можете сделать cronjob, чтобы периодически создавать список изображений и хранить их в DB/file/BDB/...

каждые полчаса должно быть хорошо, но обязательно создайте интерфейс для сброса кэша в случае добавления/удаления файла.

и затем, это также легко запустить find . - mmin -30-print0 на оболочке и добавить новые файлы.


при сохранении файла в папку, если загрузка прошла успешно, можно сохранить путь к таблице БД.

тогда вам просто нужно будет сделать запрос к базе данных, чтобы найти путь к запрошенному файлу.


Я пришел на эту страницу в поисках решения, и кажется, что fopen может сделать трюк. При использовании этого кода может потребоваться отключить ведение журнала ошибок для файлов, которые не найдены.

<?php
for ($n=1;$n<100;$n++){
clearstatcache();
$h=@fopen("files.php","r");
if ($h){
echo "F";
fclose($h);
}else{
echo "N";
}
}
?>

старый вопрос, я собираюсь добавить ответ здесь. Для PHP 5.3.8, is_file() (для существующего файла) на порядок быстрее. Для несуществующего файла время почти идентично. Для PHP 5.1 с eaccelerator, они немного ближе.

PHP 5.3.8 w & w / o APC

time ratio (1000 iterations)
Array
(
    [3."is_file('exists')"] => 1.00x    (0.002305269241333)
    [5."is_link('exists')"] => 1.21x    (0.0027914047241211)
    [7."stream_resolve_inclu"(exists)] => 2.79x (0.0064241886138916)
    [1."file_exists('exists')"] => 13.35x   (0.030781030654907)
    [8."stream_resolve_inclu"(nonexists)] => 14.19x (0.032708406448364)
    [4."is_file('nonexists)"] => 14.23x (0.032796382904053)
    [6."is_link('nonexists)"] => 14.33x (0.033039808273315)
    [2."file_exists('nonexists)"] => 14.77x (0.034039735794067)
)

PHP 5.1 w / eaccelerator

time ratio (1000x)
Array
(
    [3."is_file('exists')"] => 1.00x    (0.000458002090454)
    [5."is_link('exists')"] => 1.22x    (0.000559568405151)
    [6."is_link('nonexists')"] => 3.27x (0.00149989128113)
    [4."is_file('nonexists')"] => 3.36x (0.00153875350952)
    [2."file_exists('nonexists')"] => 3.92x (0.00179600715637)
    [1."file_exists('exists"] => 4.22x  (0.00193166732788)
)

есть несколько предостережений.
1) Не все "файлы" являются файлами, is_file () тесты для обычный файлы, не символические ссылки. Таким образом, в системе *nix вы не можете уйти просто с is_file (), если вы не обязательно что вы имеете дело только с обычными файлами. Для загрузок и т. д. Это может быть справедливым предположением или если сервер основан на Windows, который фактически не имеет символических ссылок. В противном случае вам придется проверить is_file($file) || is_link($file).

2) производительность определенно ухудшается для всех методов, если файл отсутствует и становится примерно равным.

3) Самое большое предостережение. Все методы кэшируйте статистику файла для ускорения поиска, поэтому, если файл меняется регулярно или быстро, удаляется, появляется, удаляет, то clearstatcache(); должен быть запущен, чтобы убедиться, что правильная информация о существовании файла находится в кэше. Поэтому я проверил их. Я опустил все имена файлов и все такое. Важно то, что почти все времена сходятся, за исключением stream_resolve_include, который в 4 раза быстрее. Опять же, на этом сервере есть eaccelerator, поэтому YMMV.

time ratio (1000x)
Array
(
    [7."stream_resolve_inclu...;clearstatcache();"] => 1.00x    (0.0066831111907959)
    [1."file_exists(...........;clearstatcache();"] => 4.39x    (0.029333114624023)
    [3."is_file(................;clearstatcache();] => 4.55x    (0.030423402786255)
    [5."is_link(................;clearstatcache();] => 4.61x    (0.030798196792603)
    [4."is_file(................;clearstatcache();] => 4.89x    (0.032709360122681)
    [8."stream_resolve_inclu...;clearstatcache();"] => 4.90x    (0.032740354537964)
    [2."file_exists(...........;clearstatcache();"] => 4.92x    (0.032855272293091)
    [6."is_link(...............;clearstatcache();"] => 5.11x    (0.034154653549194)
)

в принципе, идея в том, если вы на 100% уверены, что это файл, а не символическая ссылка или каталог, и, по всей вероятности, он будет существовать, а затем использовать is_file(). Вы увидите определенный выигрыш. Если файл может быть файлом или ссылкой в любой момент, то не is_file() 14х + is_link() 14х (is_file() || is_link()), и в конечном итоге будет 2x медленнее в целом. Если существование файла сильно меняется, используйте stream_resolve_include_path ().

так это зависит от вашего сценария использования.


Я думаю, что лучший способ-сохранить url-адрес изображения в базе данных, а затем поместить его в переменную сеанса, особенно когда у вас есть аутентификация. Таким образом, вам не нужно проверять каждый раз, когда страница перезагружается


насчет glob()? Но я не уверен, что это быстро.

http://www.php.net/manual/en/function.glob.php


Я даже не уверен, что это будет быстрее, но кажется, что вы все равно хотели бы проверить soooo:

создайте кэш большого массива всех путей к изображениям.

$array = array('/path/to/file.jpg' => true, '/path/to/file2.gif' => true);

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

Если вы хотите проверить, существует ли файл, загрузите кэшированный массив и сделайте просто isset () проверьте быстрый поиск индекса массива:

if (isset($myCachedArray[$imgpath])) {
    // handle display
}

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