Skip to main content
5 of 5
replaced http://stackoverflow.com/ with https://stackoverflow.com/

The file_scan_directory() is a recursive function which all files that match for a given directory. It's uses is_dir() and opendir() PHP calls which may be most time expensive in terms of I/O system calls. Simple Drupal bootstrap (e.g. time drush ev "") may call file_scan_directory over few thousands times (depending on complexity of your Drupal folder hierarchy, e.g. number of modules and its folders).

In my case I had ~1500 calls to file_scan_directory (24 seconds in total consisting 2 calls from drupal_system_listing in common.inc, then the other calls were split by recursive calls to file_scan_directory it-self.

In order to improve the performance on I/O calls, you need to implement the file caching. This can be achieved by installing and enabling OPCache (opcache.enable=1) and tweak its settings (see: How to use PHP OPCache?). Using memory-based caching such as memcached/redis is also advised.

When using command-line interface (such as drush), you should also enable opcache.enable_cli=1.

After the change you can check the more consuming syscalls using some available debuggers.

E.g.

  • On Linux using strace (hit Ctrl-C to finish):

      sudo strace -c -fp $(pgrep -n php)
    
  • On Unix using dtrace (using PHP's DTrace static probes), e.g.

      sudo dtrace -n 'inline string NAME = "php"; syscall:::entry /(NAME == strstr(NAME, execname)) || (execname == strstr(execname, NAME))/ { @num[probefunc] = count(); }'
    

You can further consider optimizing drupal_system_listing() or file_scan_directory() by implementing static cache, e.g.

--- a/includes/file.inc
+++ b/includes/file.inc
@@ -2104,6 +2104,8 @@ function file_download_access($uri) {
  *   'filename', and 'name' members corresponding to the matching files.
  */
 function file_scan_directory($dir, $mask, $options = array(), $depth = 0) {
+  static $dirs = array();
+
   // Merge in defaults.
   $options += array(
     'nomask' => '/(\.\.?|CVS)$/',
@@ -2120,7 +2122,12 @@ function file_scan_directory($dir, $mask, $options = array(), $depth = 0) {
       if (!preg_match($options['nomask'], $filename) && $filename[0] != '.') {
         $uri = "$dir/$filename";
         $uri = file_stream_wrapper_uri_normalize($uri);
-        if (is_dir($uri) && $options['recurse']) {
+
+        if (empty($dirs[$uri])) {
+          $dirs[$uri] = is_dir($uri);
+        }
+
+        if ($dirs[$uri] && $options['recurse']) {
           // Give priority to files in this folder by merging them in after any subdirectory files.
           $files = array_merge(file_scan_directory($uri, $mask, $options, $depth + 1), $files);

Or for caching file_scan_directory calls from drupal_system_listing(), then check the following patch available at: file_scan_directory should be cached.

kenorb
  • 14.3k
  • 10
  • 98
  • 168