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