【问题标题】:file_exists() is too slow in PHP. Can anyone suggest a faster alternative?file_exists() 在 PHP 中太慢了。任何人都可以提出更快的替代方案吗?
【发布时间】:2010-12-15 02:12:05
【问题描述】:

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

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

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

【问题讨论】:

  • 如果你的代码中最慢的部分只增加了20ms的总加载时间,你应该出去喝杯啤酒,而不是太担心它重新向 SO 发布问题 ;-)
  • 您使用的是什么文件系统? - file_Exists() 的速度主要取决于 stat() 系统调用的速度。目录中有多少个文件? (取决于文件系统,文件数量对 stat() 速度有影响)
  • 每 1/2 毫秒,你可以在一秒钟内完成 2000 个 file_exists
  • 哦,引用维基百科... 眨眼的平均长度是 300 到 400 毫秒。 不知道为什么,但感觉很适合与您分享。跨度>
  • 我实际上已经尝试过一次,我的函数花费了 file_exists() 执行时间的 11 倍,所以我最好的选择是更好地使用缓存,或者想出另一种方法。

标签: php performance file-exists


【解决方案1】:

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

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

【讨论】:

  • 我想我应该接受性能很好并保持原样。不过,我可能会将文件分解为更多文件夹,因为这可能会有所帮助。
  • 根据文档,只有在 file_exists() 返回 true 时才会发生缓存。因此,如果您碰巧检查不存在的文件,该功能将每次检查。您可以在 file_exists() 返回 false 时创建指向虚拟图像的符号链接,以便缓存后续调用。 (这可能会导致其他问题)
【解决方案2】:

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

realpath() 也一样,但我不知道它是否更快。

但是文件访问 I/O 总是很慢。通常,硬盘访问IS比在处理器中计算某些东西要慢。

【讨论】:

  • 好提示。我已经提供了文件的完整路径名(主要是为了避免包含路径设置的不可靠性质)。
  • 关于这个问题的一个帖子和一个要测试的脚本:bytes.com/topic/php/answers/…
  • 我可能是错的,但是要知道文件是否存在需要检查 FS 索引表,因此它不应该是需要文件“读取”或“写入”操作的真正 IO 操作在磁盘上。
【解决方案3】:

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

在测试中使用了绝对路径。 测试源为here。 PHP版本:

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

【讨论】:

    【解决方案4】:

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

    如果您只是对回退到这个虚拟图像感兴趣,您可能需要考虑让客户端通过在 file-not-found 上的重定向(到虚拟图像)与服务器进行协商。

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

    只是一个想法。

    【讨论】:

    • +1 表示聪明。现在我很好奇如果你用 404 响应传回 jpg 数据会发生什么。毕竟,这是 OP 正在寻找的 404 类型的行为。
    • 应该可以正常渲染。基本上,自定义 404 页面的行为相同;如果这样提供,则呈现为 XHTML。不过还没有测试。
    【解决方案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'  
    

    【讨论】:

      【解决方案6】:

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

      文件名.jpg -> 012345 -> /01/23/45.jpg

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

      【讨论】:

        【解决方案7】:

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

        this thread

        【讨论】:

          【解决方案8】:

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

          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

          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() 测试 regular 文件,而不是符号链接。所以在 *nix 系统上,你不能只使用 is_file() ,除非你确定你只处理普通文件。对于上传等,这可能是一个公平的假设,或者如果服务器是基于 Windows 的,它实际上没有符号链接。否则,您必须测试is_file($file) || is_link($file)

          2) 如果文件丢失并且大致相等,所有方法的性能肯定会下降。

          3) 最大的警告。所有方法都会缓存文件统计信息以加快查找速度,因此如果文件定期或快速更改、删除、重新出现、删除,则必须运行clearstatcache(); 以确保缓存中存在正确的文件存在信息。所以我测试了那些。我省略了所有文件名等。重要的是几乎所有时间都收敛,除了 stream_resolve_include,它的速度是 4 倍。同样,这个服务器上有加速器,所以 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 倍。如果文件的存在发生了很多变化,请使用 stream_resolve_include_path()。

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

          【讨论】:

            【解决方案9】:

            我不完全知道你想做什么,但你可以let the client handle it

            【讨论】:

              【解决方案10】:

              如果您只检查现有的files,请使用is_file()file_exists() 检查现有文件或目录,所以 is_file() 可能会快一点。

              【解决方案11】:

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

              【讨论】:

              • 我假设这个哈希值会存储在 APC 的某个地方……或其他某种共享内存中。
              【解决方案12】:

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

              本地和远程更快!

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

              【讨论】:

                【解决方案13】:

                在 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 进行检查应该是毫无疑问的。检查你是否需要。

                【讨论】:

                  【解决方案14】:

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

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

                  【讨论】:

                    【解决方案15】:

                    您可以执行 cronjob 来定期创建图像列表并将它们存储在 DB/file/BDB/...

                    每半小时应该没问题,但一定要创建一个接口来重置缓存,以防文件添加/删除。

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

                    【讨论】:

                      【解决方案16】:

                      当您将文件保存到文件夹时,如果上传成功,您可以将路径存储到 DB Table。

                      然后您只需查询数据库即可找到所请求文件的路径。

                      【讨论】:

                      • 数据库也存储在磁盘上*,你确定它会更快吗? * 通常
                      【解决方案17】:

                      我来到这个页面寻找解决方案,似乎 fopen 可以解决问题。如果您使用此代码,您可能希望对未找到的文件禁用错误日志记录。

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

                      【讨论】:

                        【解决方案18】:

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

                        【讨论】:

                          【解决方案19】:

                          glob() 呢?但我不确定它是否很快。

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

                          【讨论】:

                          • glob() 与 file_exists() 相比简直是恐龙!我认为在这种情况下它不会有帮助。
                          【解决方案20】:

                          我什至不确定这是否会更快,但看起来您仍然想对 soooo 进行基准测试:

                          构建一个包含所有图像路径的大型数组的缓存。

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

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

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

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

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

                          【讨论】:

                            猜你喜欢
                            • 2015-05-30
                            • 2011-01-05
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 2010-12-21
                            • 1970-01-01
                            相关资源
                            最近更新 更多