我注意到使用xhprof file_scan_directory()
加载首页时需要花费10秒钟以上的时间来执行。为什么要花这么长时间?
这是xhprofile的输出:
我注意到使用xhprof file_scan_directory()
加载首页时需要花费10秒钟以上的时间来执行。为什么要花这么长时间?
这是xhprofile的输出:
Answers:
听起来您受Drupal 7中一个已知问题的影响。
最有可能的是,当缺少多个模块时,您点击避免重新扫描模块目录。如果您的安装中缺少一些模块,则会发生这种情况。尝试检查您的系统表:
SELECT name, filename FROM system WHERE type = 'module' AND status = 1 ORDER BY filename
并清除文件系统中仍然启用但缺少的所有模块。
总体而言,Drupal 7比Drupal 6在资源上更加友好且可扩展,除了一些不幸的回归之类。
查看这些功能,看起来确实缺少模块或模块的单个文件。看一下drupal_get_filename(),它会调用drupal_system_listing(),如果找不到所请求的文件,则会调用此函数。在调用drupal_system_listing()之前添加一个dpm(func_get_args()),它应该告诉您找不到的文件。
可能会出现此问题的原因有很多,令我非常沮丧的是,我现在对这些原因有些了解。令人沮丧的是,如果您在将Drupal核心升级到7.33+之后刚刚注意到此问题,那么即使您没有升级该模块,这也可能是任何模块的错字。
您可能希望首先检查@Berdir提到的已知错误,尤其是如果您最近正在从代码库中删除“未使用”的模块时,尤其如此。要查明是否有已启用但已从文件系统中删除的模块,可以运行一个脚本(例如此处提到的脚本)-或使用为在带有drush的系统上进行多站点安装而编写的我的脚本来运行从Drupal基本目录:
find sites -maxdepth 1 -iname '*.*' -type d | sed -rne 's:sites/(.+):echo \1; drush @\1 sqlq "select filename from system where status = 1" | grep "/" | sed -rne "s_(.+)_test -f \\1 || echo \\1_p" | bash:p' | bash
或以下内容:
while read -r file; do [ -f "$file" ] || echo "$file is missing."; done < <(drush sqlq "SELECT filename FROM system WHERE status = 1")
如果找到已从代码库中删除的模块,请按照@Berdir提到的问题中的说明进行操作。
如果不是那样,您的情况很可能是由编码错误引起的,例如文件已被删除但仍通过drupal_add_js调用(来自问题#1082892的注释19)添加,或者模块或主题中的拼写错误,例如imagecache_actions
(请参阅https://drupal.org/node/2381357)。
无论如何,要弄清楚为什么会这样,您需要确切知道Drupal找不到哪个文件。因此,根据Berdir的评论,您可以drupal_get_filename
在bootstrap.inc
调用之前添加日志或消息调用来暂时破解drupal_system_listing()
。如果您已安装Devel模块,则dpm
可以使用;如果没有,则可以使用drupal_set_message
或syslog。例子:
dpm(func_get_args());
drupal_set_message(implode(', ', func_get_args()));
syslog(LOG_WARNING, implode(', ', func_get_args()));
一旦知道了Drupal的需求,就可以确定从那里去哪儿了。我的问题是由调用包含不存在的模块中的文件引起的imagcache_actions
(请注意拼写错误)。因此,我imagecache_actions
在我的代码库中进行了搜索(例如grep -r imagcache_actions .
),发现1.4版本的版本imagecache_canvasactions.module
在任何函数调用之外都在文件范围内使用错字来使用module_load_include。同样,仅在更新到Drupal 7.33+之后才暴露此错误。我发现已经为创建了一个问题imagecache_actions
,应用了该补丁程序,然后又恢复了正常工作。
该file_scan_directory()
是所有的文件匹配一个给定的目录,递归函数。它的使用is_dir()
和opendir()
PHP调用就I / O系统调用而言可能是最耗时的。简单的Drupal引导程序(例如time drush ev ""
)可能调用file_scan_directory
数千次(具体取决于您Drupal文件夹层次结构的复杂性,例如模块及其文件夹的数量)。
在我的情况下,我有约1500次调用file_scan_directory
(总共24秒,其中包含来自drupal_system_listing
in的2次调用common.inc
,然后其他调用被递归调用file_scan_directory
自身分解)。
为了提高I / O调用的性能,您需要实现文件缓存。这可以通过安装和启用OPCache(opcache.enable=1
)并对其设置进行调整来实现(请参阅:如何使用PHP OPCache?)。还建议使用基于内存的缓存,例如memcached / redis。
使用命令行界面(例如drush
)时,还应该启用opcache.enable_cli=1
。
更改之后,您可以使用一些可用的调试器检查更耗时的系统调用。
例如
在Linux上使用strace
(按Ctrl- C完成):
sudo strace -c -fp $(pgrep -n php)
在Unix上使用dtrace
(使用PHP的DTrace静态探针),例如
sudo dtrace -n 'inline string NAME = "php"; syscall:::entry /(NAME == strstr(NAME, execname)) || (execname == strstr(execname, NAME))/ { @num[probefunc] = count(); }'
您可以进一步考虑优化drupal_system_listing()
或file_scan_directory()
通过实现静态缓存,例如
--- 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);
或者,要缓存file_scan_directory
来自的调用drupal_system_listing()
,请检查以下修补程序:file_scan_directory应该被缓存。