file_exists()在PHP中太慢了。有谁能建议更快的替代方案?

时间:2009-11-10 15:23:20

标签: php performance file-exists

在我们的网站上显示图像时,我们通过调用file_exists()来检查文件是否存在。如果文件丢失,我们会回到虚拟图像。

然而,分析表明,这是生成我们页面的最慢部分,file_exists()每个文件占用 1/2 ms 。我们只测试了40个左右的文件,但这仍然会将 20ms 推到页面加载时间。

任何人都可以建议一种方法让它更快吗?有没有更好的方法来测试文件是否存在?如果我构建某种缓存,我应该如何保持同步。

20 个答案:

答案 0 :(得分:25)

file_exists()应该是一个非常便宜的操作。另请注意,file_exists构建自己的缓存以帮助提高性能。

请参阅:http://php.net/manual/en/function.file-exists.php

答案 1 :(得分:20)

使用绝对路径!根据您的include_path设置,如果您检查相关文件路径,PHP会检查所有(!)这些目录!在检查存在之前,您可能会暂时取消include_path

realpath()做同样的事情,但我不知道它是否更快。

但文件访问I / O总是很慢。通常,硬盘访问 IS 比计算处理器中的内容慢。

答案 2 :(得分:17)

检查本地文件是否存在的最快方法是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%

在测试中使用的绝对路径。 测试来源是here。 PHP版本:

  

PHP 5.4.23-1~dotdeb.1(cli)(建于2013年12月13日21:53:21)
  版权所有(c)1997-2013 PHP小组
  Zend Engine v2.4.0,版权所有(c)1998-2013 Zend Technologies

答案 3 :(得分:11)

  

如果文件丢失,我们会回到虚拟图像

如果您只想回到这个虚拟图像,您可能需要考虑让客户端通过在文件未找到的重定向(到虚拟图像)与服务器协商。

这样你只会有一点重定向开销和客户端不明显的延迟。至少你会摆脱“昂贵”(我不知道)对file_exists的呼唤。

只是一个想法。

答案 4 :(得分:5)

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'  

答案 5 :(得分:3)

PHP自动缓存

file_exists()。我不认为你会在PHP中找到一个更快的函数来检查文件是否存在。

请参阅this thread

答案 6 :(得分:3)

创建一个散列例程,用于将文件分片到多个子目录中。

  

filename.jpg - &gt; 012345 - &gt; /01/23/45.jpg

此外,您可以使用mod_rewrite将占位符图像返回到图像目录404的请求。

答案 7 :(得分:2)

答案 8 :(得分:2)

如果您只检查现有的files,请使用is_file()file_exists()检查现有文件OR目录,因此is_file()可能会更快一些。

答案 9 :(得分:1)

如果您想检查图像文件是否存在,更快方式是使用 getimagesize

本地和远程更快!

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

答案 10 :(得分:1)

老问题,我将在这里添加一个答案。对于php 5.3.8,is_file()(对于现有文件)的速度要快一个数量级。对于不存在的文件,时间几乎相同。对于带有eaccelerator的PHP 5.1,它们距离更近一些。

PHP 5.3.8 w&amp;没有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()14x + is_link()14x(is_file() || is_link()),最终总体上会慢2倍。如果文件的存在改变A LOT,则使用stream_resolve_include_path()。

所以这取决于您的使用场景。

答案 11 :(得分:1)

它们都在同一个目录中吗?如果是这样,可能值得获取文件列表并将它们存储在散列中并与之进行比较而不是所有的file_exists查找。

答案 12 :(得分:0)

我发现每次通话1 / 2ms非常非常实惠。我不认为有更快的替代方案,因为文件功能非常接近处理文件操作的较低层。

但是,您可以为file_exists()编写一个包装器,将结果缓存到内存缓存或类似工具中。这应该可以减少日常生活中几乎没有的时间。

答案 13 :(得分:0)

你可以做一个cronjob定期创建一个图像列表并将它们存储在DB / file / BDB /...中。

每半小时应该没问题,但在文件添加/删除的情况下,请确保创建一个重置缓存的界面。

然后,运行查找也很容易。 shell上的-mmin -30 -print0并添加新文件。

答案 14 :(得分:0)

将文件保存到文件夹时,如果上传成功,则可以将路径存储到数据库表。

然后您只需要对数据库进行查询,以便找到所请求文件的路径。

答案 15 :(得分:0)

glob()怎么样?但我不确定它是否很快。

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

答案 16 :(得分:0)

我来到这个寻找解决方案的页面,似乎fopen可以做到这一点。如果使用此代码,则可能需要为未找到的文件禁用错误日志记录。

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

答案 17 :(得分:0)

我认为最好的方法是将图片网址保留在数据库中,然后将其放入会话变量中,尤其是在您进行身份验证时。这些方式您不必每次重新加载页面时都要检查

答案 18 :(得分:0)

在 2021 年,也就是提出这个问题 12 年后,我有同样的用例。在我决定显示什么之前,我会循环查看 file_exist 大约 40 张图片。

以毫秒为单位的数字 (PHP 7.4):

  • 在本地开发机器(Win10、WAMP、Samsung SSD)上:每个图像大约 0.1 (1/10) 毫秒,文件夹中大约 1000 个图像;
  • 在服务器上(非常基本的廉价服务器,VPS 1 Intel Xeon,RAM 2GB,SSD,Ubuntu,LAMP):每个图像大约 0.01 (1/100) 毫秒,文件夹中有 14,000 个图像;

服务器比开发机器快 10 倍,与整体 UX 性能 POV 完全没有区别,其中 30-50 毫秒是第一个明显的阈值。

在服务器上检查 40 张图像的数组时,我花了 0.4 毫秒来检查它们中是否有人不存在。顺便说一句,无论某些图像是否存在,性能都没有区别。

因此,由于磁盘性能的原因,是否使用 file_exist 进行检查应该是没有问题的。检查您是否需要。

答案 19 :(得分:-1)

我甚至不确定这是否会更快但似乎你仍然希望以soooo为基准:

构建大量所有图像路径的缓存。

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

根据您的要求更新缓存每小时每日。您可以使用 cron 来运行PHP脚本,该脚本将递归遍历files目录以生成路径数组。

如果要检查文件是否存在,请加载缓存的数组并执行简单的 isset()检查快速数组索引查找:

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

加载缓存仍然会有开销,但希望它足够小以留在内存中。如果您要在页面上检查多个图像,您可能会注意到更大的收益,因为您可以在页面加载时加载缓存。