在PHP中,file_exists()函数速度太慢了。有没有人能提供一个更快的替代方案?

45

在我们网站上显示图片时,我们使用 file_exists() 来检查文件是否存在。如果文件不存在,我们将使用一个虚拟图片。

然而,分析表明这是生成我们的页面最慢的部分之一,file_exists() 每个文件需要 1/2 毫秒 的时间。虽然我们只测试了大约40个文件,但这仍然使页面加载时间增加了20毫秒

有谁能想出一种更快的方法吗? 是否有更好的方法来测试文件是否存在?如果我建立一个缓存,如何保持它同步。


71
如果你的代码中最慢的部分仅添加了20毫秒的总加载时间,那么你应该出去喝一杯啤酒,而不是担心它到了发帖询问的程度。;-) - Duroth
2
你使用的是哪个文件系统?- file_Exists() 函数的速度应该主要取决于 stat() 系统调用的速度。目录中有多少个文件?(根据文件系统,文件数量对 stat() 速度有影响) - johannes
2
每个文件的存在检查只需要0.5毫秒,您可以在一秒钟内完成2000次。 - Adam Hopkinson
34
哦,引用维基百科……眨眼的平均时间为300至400毫秒。不确定为什么,但感觉与你分享这个信息很合适。 - Duroth
1
目录中有很多文件(数千个),这可能对性能的影响比其他任何因素都更大。我可能会考虑将其拆分成小批量的文件。 - Rik Heywood
显示剩余4条评论
20个回答

34

4
我想我应该接受目前的表现还可以,就按照现在这样子。不过我可能会把文件分成更多的文件夹,因为这样可能会有所帮助。 - Rik Heywood
5
根据文档,只有当file_exists()返回true时才会进行缓存。因此,如果您检查不存在的文件,则该函数每次都会进行检查。如果file_exists()返回false,您可以创建到虚拟图像的符号链接,以便后续调用将被缓存(这可能会引起其他问题)。 - Patrick Forget
缓存有多持久?文档没有说明。它只在当前请求中存在,还是跨请求持久存在?我认为应该是前者,这似乎对 OP 的情况没有帮助。 - Ian Dunn

23

请使用绝对路径!根据您的include_path设置,如果您使用相对文件路径,则PHP会检查所有这些目录!在检查存在性之前,您可能需要临时取消设置include_path

realpath()执行相同的操作,但我不知道它是否更快。

但是,文件访问I/O始终很慢。硬盘访问通常在处理器中计算某些内容要慢。


好的提示。虽然我已经提供了完整的文件路径名(主要是为了避免包含路径设置不可靠的性质)。 - Rik Heywood
1
一个关于这个问题的帖子和用于测试的脚本:http://bytes.com/topic/php/answers/10394-file_exists-expensive-performance-terms - powtac
我可能错了,但是要知道文件是否存在需要在FS索引表中进行检查,因此它不应该是一个真正的IO操作,也不需要在磁盘上执行“读”或“写”文件操作。 - Niki Romagnoli

22

检查本地文件是否存在的最快方法是使用stream_resolve_include_path()函数:

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

性能结果 stream_resolve_include_path()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) (built: Dec 13 2013 21:53:21)
版权所有 (c) 1997-2013 PHP Group
Zend引擎 v2.4.0,版权所有 (c) 1998-2013 Zend Technologies


11

如果文件丢失,我们会退回到一个虚拟的图片

如果你只是想退回到这个虚拟的图片,你可以考虑通过重定向(到虚拟图片)来让客户端和服务器进行协商。

这样你只会有一点点重定向开销和客户端上不可见的延迟。至少你可以摆脱“昂贵”的(其实不是我知道的)调用file_exists

只是一个想法。


2
+1 聪明。现在我很好奇,如果您使用 404 响应返回 jpg 数据会发生什么。毕竟,OP 寻找的是 404 类型的行为。 - timdev
1
应该可以正常呈现。基本上,自定义404页面的行为与此相同;如果作为XHTML提供,则会呈现为XHTML。尽管我还没有测试过。 - jensgram

6

使用 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'  

@include 只是不可接受的,无论性能如何,都不应该被使用。 - Your Common Sense

4
创建一个哈希函数,将文件分散到多个子目录中。
例如:filename.jpg -> 012345 -> /01/23/45.jpg
此外,您可以使用mod_rewrite来返回占位符图像,以响应请求您的图像目录中404的情况。

3

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

参见此线程


2

关于这个老问题,我来添加一个答案。对于php 5.3.8版本,is_file()函数(检查文件是否存在)的速度快了一个数量级。对于不存在的文件,时间几乎相同。对于安装了eaccelerator的PHP 5.1版本,它们的速度更接近。

PHP 5.3.8 带 & 不带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搭配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,所以你的情况可能不一样。

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() || is_link()需要14倍的时间,总体上会变慢2倍。如果文件的存在经常发生变化,则使用stream_resolve_include_path()。
所以它取决于您的使用场景。

现在,这是旧的 答案 - Your Common Sense

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


2

我不太确定你想要做什么,但你可以让客户端来处理它。

只要让客户端来处理即可。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接