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.