The [`file_scan_directory()`][1] is a recursive function which all files that match for a given directory. It's uses [`is_dir()`][2] and [`opendir()`][3] 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`][4] 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?][5]). 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 <kbd>Ctrl</kbd>-<kbd>C</kbd> to finish): sudo strace -c -fp $(pgrep -n php) - On Unix using `dtrace` (using [PHP's DTrace static probes][6]), 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()`][7], then check the following patch available at: [file_scan_directory should be cached][8]. [1]: https://api.drupal.org/api/drupal/includes%21file.inc/function/file_scan_directory/7 [2]: http://php.net/is_dir [3]: http://php.net/opendir [4]: https://api.drupal.org/api/drupal/includes%21common.inc/function/drupal_system_listing/7 [5]: https://stackoverflow.com/q/17224798/55075 [6]: http://php.net/manual/en/features.dtrace.dtrace.php [7]: https://api.drupal.org/api/drupal/includes%21common.inc/function/drupal_system_listing/7.x [8]: https://www.drupal.org/node/2710289