问题描述

我们的团队正在开发 WordPress 插件,并在几个独立的服务器上提供托管的实例。我们的 WordPress 安装由 Git 管理,所有服务器具有相同的来源和 WordPress 安装部署,只有域& 数据库中的实际数据有所不同。对于每次安装,MySql 都在同一主机上运行。 WordPress 正在每个服务器上运行。

然而,在 Windows Server 2008 RC2 上部署了此设置后,我们注意到与其他服务器相比性能差异很大:页面生成时间从平均值上升。使用 PHP 生成的页面为 400 到 4000-5000ms 。对于由 Apache 提供的静态资源,速度与 linux 上的速度大致相同。

所以我们采取了一些步骤来缩小问题:

  1. 确保没有 antivir-software 运行或其他 Windows 域的东西干扰

  2. 收集分析数据以在脚本执行期间识别时间盘点

  3. 测试不同的服务器硬件设置

  4. Double-check 的 Apache 和 PHP 配置都有明显的配置错误

经过一些分析,我们很快就注意到,正则表达式的评估在我们的 Windows 机器上是非常缓慢的。评估 10.000 正则表达式 (preg_match) 在 Linux 上大约 90ms,Windows 上为 3000ms 。

配置文件,系统测试和配置详细信息如下。我们不想优化这个脚本 (我们知道如何做) 。我们希望让脚本在 Windows 上运行大致相同的速度 (在与 opcache /… 相同的设置中) 。也不需要优化脚本的内存占用。

更新:一段时间后,系统似乎耗尽内存,触发内存异常和随机分配。有关详细信息,请参阅下文。重新启动 Apache /PHP 解决了现在的问题。

追溯到_get_browser 是:

File (called from)
require wp-blog-header.php (index.php:17)
wp (wp-blog-header.php:14)
WP->main (functions.php:808)
php::do_action_ref_array (class-wp.php:616)
php::call_user_func_array (wp-includes/plugin:507)
wp_slimstat::slimtrack  (php::internal (507))
wp_slimstat::_get_browser (wp-slimstat.php:385)

更新 2:有些原因我不记得我们回到在我们的服务器上激活 PHP 作为 Apache 模块 (同样的,表现不佳) 。但是今天他们运行得很快 (〜 1sec /request) 。添加 Opcache 可将其降低至〜 400ms /req 。 Apache /PHP /Windows 保持不变。

1) 分析结果

在所有机器上使用 XDebug 进行分析。通常我们只收集了几个游戏 – 那些足以显示大部分时间 (50%+) 花费的位置:WordPress 插件 wp-slimstats 的方法 [get_browser][1]

protected static function _get_browser(){
    // Load cache
    @include_once(plugin_dir_path( __FILE__ ).'databases/browscap.php');
    // browscap.php contains $slimstat_patterns and $slimstat_browsers

    $browser = array('browser' => 'Default Browser', 'version' => '1', 'platform' => 'unknown', 'css_version' => 1, 'type' => 1);
    if (empty($slimstat_patterns) || !is_array($slimstat_patterns)) return $browser;

    $user_agent = isset($_SERVER['HTTP_USER_AGENT'])?$_SERVER['HTTP_USER_AGENT']:'';
    $search = array();
    foreach ($slimstat_patterns as $key => $pattern){
        if (preg_match($pattern . 'i', $user_agent)){
            $search = $value = $search + $slimstat_browsers[$key];
            while (array_key_exists(3, $value) && $value[3]) {
                $value = $slimstat_browsers[$value[3]];
                $search += $value;
            }
            break;
        }
    }

    // Lots of other lines to relevant to the profiling results
  }

这个类似于 PHP get_browser 的功能检测浏览器的功能和操作系统。大多数脚本执行时间都花在这个 foreach 循环中,评估所有这些 preg_match(约 8000 – 10000 页每页请求) 。这在 Linux 上大约需要 90ms,Windows 上需要 3000ms 。所有测试设置的结果相同 (图片显示两次执行的数据):

当然,加载两个巨大的阵列需要一些时间。评估正则表达式。但是我们期望他们在 Linux 和 Windows 上大致相同的时间。这是一个 linux vm 的分析结果 (仅一页请求) 。差异很明显:

另一个时间杀手实际上是 Object-Cache WordPress 使用的:

function get( $key, $group = 'default', $force = false, &$found = null ) {
    if ( empty( $group ) )
        $group = 'default';

    if ( $this->multisite && ! isset( $this->global_groups[ $group ] ) )
        $key = $this->blog_prefix . $key;

    if ( $this->_exists( $key, $group ) ) {
        $found = true;
        $this->cache_hits += 1;
        if ( is_object($this->cache[$group][$key]) )
            return clone $this->cache[$group][$key];
        else
            return $this->cache[$group][$key];
    }

    $found = false;
    $this->cache_misses += 1;
    return false;
}

时间花在这个函数本身 (3 个脚本执行) 中:

在 linux 上

最后一个真正的大时代杀手是翻译。每个翻译,从内存加载,在 WordPress 中从 0.2ms 到 4ms 的任何东西:

在 linux 上

2) 测试系统

为了确保虚拟化或 Apache 确实影响到这一点,我们在几个设置中进行了测试。所有设置都禁用了 Antivir:

  • Linux Debian,Apache 2& PHP 最新稳定版本。对于在虚拟机中运行的开发人员,对于分段/实时服务器,这是一样的。作为所需性能的参考系。无论是在我们的办公室还是在一些主机提供 (共享空间) 。 Windows 系统具有 4GB 和 8GB 的 RAM 之间,所有的内存使用率都在 50%以下。虚拟化永远不会运行 Windows& Apache 在同一时间。

  • 在 T-Systems(托管虚拟化服务器) 上运行的 Life-Servers,在 VMWare Player

    • Win 2008 R2 。 Apache 2.2.25 + PHP 5.4.26 NTS,VC9 作为 fastcgi 模块

    • Win 2008 R2 。 Apache 2.2.25 + PHP 5.5.1 NTS,VC11 作为 fastcgi 模块

    • Win 2008 R2 。 Apache 2.2.25 + PHP 5.5.1 NTS,VC11 作为 apache 模块

    • Win 2008 R2,Apache 2.2.25 + PHP 5.5.11 TS,VC11 作为 apache 模块 (这是我在更新 2 中提到的快速)

  • 在本地机器上,Host:OpenSuse,Virtualization:VMWare 播放器,与 @ T-Systems 相同。为了避免基础设施影响我们:

    • Win 2008 R2 。 Apache 2.2.25 + PHP 5.4.26 NTS,VC9 作为 fastcgi 模块

    • Win 2008 R2 。 IIS7 + PHP 5.4.26 NTS,VC9 作为 fastcgi 模块 (带和不带 wincache)

    • Win 2012. IIS * + PHP 5.5.10 NTS,VC11 作为 fastcgi 模块 (带和不带 wincache)

  • 在没有虚拟化的本地机器上

    • Win 2008 R2 。 Apache 2.2.25 + PHP 5.4.26 NTS,VC9 作为 fastcgi 模块

如上所述的分析结果在不同的系统上是相同的 (〜 10%的推导) 。 Windows 总是比 Linux 慢的一个重要因素。

使用全新安装的 WordPress& Slimstats 导致约相同的结果。重写代码在这里不是一个选项。

更新:同时我们发现了另外两个 Windows 系统 (Windows 2008 R2,VM& Phys),这个完整的堆栈运行得相当快。相同的配置。

更新 2:在 Life-Servers 上运行 PHP 作为 apache 模块的速度比 fastcgi 方法稍快一些:下降到〜 2 秒,减少 50%。

内存不足

一段时间后,我们的 Live-Server 停止工作,触发这些内存不足的例外:

PHP Fatal error:  Out of memory (allocated 4456448) (tried to allocate 136 bytes)
PHP Fatal error:  Out of memory (allocated 8650752) (tried to allocate 45 bytes)
PHP Fatal error:  Out of memory (allocated 6815744) (tried to allocate 24 bytes)

这发生在随机的脚本位置。显然,Zend 内存管理器不能分配更多的内存,尽管这些脚本将被允许。当时如果事件发生,服务器有大约 50%的可用 RAM(2GB +) 。所以服务器实际上没有用完 ram 。现在重新启动 Apache /PHP 解决了这个问题。

不知道这个问题是否与这里的性能问题有关。然而,由于这两个问题似乎都是 memory 相关的,它包括在这里。特别是我们将尝试重现提供体面表现的 Windows-Tests 的设置。

3)Apache& PHP 配置

可能没有任何常见的陷阱。 Output-Buffering 启用 (默认),禁止多重镜像覆盖,… 如果有任何选项感兴趣,我们将乐意提供它们。

httpd.exe -V 的输出

Server version: Apache/2.4.7 (Win32)
Apache Lounge VC10 Server built:   Nov 26 2013 15:46:56
Server's Module Magic Number: 20120211:27
Server loaded:  APR 1.5.0, APR-UTIL 1.5.3
Compiled using: APR 1.5.0, APR-UTIL 1.5.3
Architecture:   32-bit
Server MPM:     WinNT
  threaded:     yes (fixed thread count)
    forked:     no
Server compiled with....
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses disabled)
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=256
 -D HTTPD_ROOT="/apache"
 -D SUEXEC_BIN="/apache/bin/suexec"
 -D DEFAULT_PIDLOG="logs/httpd.pid"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_ERRORLOG="logs/error.log"
 -D AP_TYPES_CONFIG_FILE="conf/mime.types"
 -D SERVER_CONFIG_FILE="conf/httpd.conf"

mpm_winnt_module 配置:

<IfModule mpm_winnt_module>
    ThreadsPerChild 150
    ThreadStackSize 8388608
    MaxConnectionsPerChild 0
</IfModule>

摘录 php.ini:

realpath_cache_size = 12M
pcre.recursion_limit = 100000

4) 目前怀疑的原因

老假设:

All three examples heavily rely on big arrays and string operations. That some kind seems to be the common factory. As the implementation works ok’ish on Linux, we suspect this to be a memory problem on Windows. Given there is no database interaction at the pin-pointed locations, we don’t suspect the database or Server <-> PHP integration to be the problem. Somehow PHP’s memory interaction just seems to be slow. Maybe there is someone interfering with the memory on Windows making access dramatically slower?

旧假设 2:

As the same stack runs fine on other Windows machines we assume the problem to be somewhere in the Windows configuration.

新假设 3:

Actually I am out of assumptions. Why would run PHP that much slower as fastcgi then as apache module>

有关如何验证这一点或在这里找到真正问题的任何想法?任何帮助或解决这个问题的方向是非常受欢迎的。

最佳解决方法

Windows 在各种情况下都有许多限制,防止,保护,控制和使用计算机的服务/策略。

一个好的微软认证专家将能够在几分钟内解决您的问题,因为他们将有经验来确定哪些设置/服务/策略来检查和禁用/启用/更改设置,以便 PHP 脚本执行得更快。

在我的 memory 中,我只能建议你检查处理 RAM,硬盘驱动器访问,环境变量,限制和安全 (如防火墙) 的所有内容。一切可能影响 php 脚本的执行,从一些远程过程调用策略开始,并以操作堆栈内存结束。

逻辑是 php.exe 调用一些外部的.dll 文件来执行一些操作,可能会对操作系统完成的方式进行检查,这样可以通过这样的.dll 缓慢发送请求,并收到来自它的响应。如果.dll 使用硬盘来访问某些东西 – 硬盘访问策略进入现场。而且,RAM 中的所有内容如何位于 RAM 或 hard-drive 缓存中。应用策略线程策略适用于应用程序的最大百分比限制。

我不是说 Windows-based 主机是坏的,只是为了一般的管理员设置正确很难。如果您拥有微软专家,他可以将您的服务器调整为与 Linux-based 服务器一样快。

次佳解决方法

  • 启用 APC,当使用 PHP5.4

    • 如果您没有注意到速度增益,当 APC 打开时,某些配置错误 [APC]
      extension=php_apc.dll
      apc.enabled=1
      apc.shm_segments=1
      apc.shm_size=128M
      apc.num_files_hint=7000
      apc.user_entries_hint=4096
      apc.ttl=7200
      apc.user_ttl=7200

  • 在 PHP 5.5 [Zend]
    zend_extension=ext/php_zend.dll
    zend_optimizerplus.enable=1
    zend_optimizerplus.use_cwd=1
    zend_optimizerplus.validate_timestamp=0
    zend_optimizerplus.revalidate_freq=2
    zend_optimizerplus.revalidate_path=0
    zend_optimizerplus.dups_fix=0
    zend_optimizerplus.log_verbosity_level=1
    zend_optimizerplus.memory_consumption=128
    zend_optimizerplus.interned_strings_buffer=16
    zend_optimizerplus.max_accelerated_files=2000
    zend_optimizerplus.max_wasted_percentage=25
    zend_optimizerplus.consistency_checks=0
    zend_optimizerplus.force_restart_timeout=60
    zend_optimizerplus.blacklist_filename=
    zend_optimizerplus.fast_shutdown=0
    zend_optimizerplus.optimization_level=0xfffffbbf
    zend_optimizerplus.enable_slow_optimizations=1
    opcache.memory_consumption=128
    opcache.interned_strings_buffer=8
    opcache.max_accelerated_files=10000
    opcache.revalidate_freq=60
    opcache.fast_shutdown=1
    opcache.enable_cli=1
    上启用 Zend Opcode

  • 禁用 Wordpress 扩展 step-wise,找到内存使用怪物

  • 设置 Wordpress:define('WP_MEMORY_LIMIT', '128M');,除非您使用图像转换插件应该足够了

  • 在 php.ini ini_set('memory_limit', -1); 中设置无限内存

  • 配置文件没有运行 Xdebug,这听起来很疯狂,但调试器本身有很大的影响

  • 使用 memory_get_usage 并在整个系统上传播调用来查找代码位置,其中内存泄漏

  • zend.enable_gc=1 一个尝试,脚本会更慢,但使用更少的内存

  • 也许只是禁用在 SlimStats 设置中检查用户浏览器。

  • 如果这是不可能的,尝试覆盖 SlimStats getBrowser() 功能,使用 faster getBrowser() substitute

  • 为了比较 user-agent 取样器的速度,参见 https://github.com/quentin389/ua-speed-tests

  • https://github.com/garetjax/phpbrowscap

第三种解决方法

我在 Github 上看了一下这个插件:

https://github.com/wp-plugins/wp-slimstat

并且包含的​​违规文件是在某种程度上已经被细化的文件,真的是数据 (而不是代码),有 5 个变体,每个文件大约是 400KB

还有 maxMind.dat 文件是 400KB,虽然我不知道它是否同时使用。

您正在使用旧版本的插件,3.2.3 版本,并且有一个更新的可能会解决您的问题。

比较差异是很困难的,因为作者或谁没有保持 git 历史的顺序,所以我不得不手动区分文件。大多数与_get_browser 相关的更改似乎都在添加缓存。

可能的加载该文件解析缓慢,但我希望 PHP 在两个平台上以相同的速率加载这两个文件,这些 IO 缓存正在工作。

编辑看起来有点接近,可能无法解决你的问题。那些文件基本上是大型正则表达式查找表。你的 Linux 系统上有一个 APC 缓存吗? APC 缓存可能会使 PHP 文件缓存 (尽管不是编译的 regex 模式)

参考文献

注:本文内容整合自 Google/Baidu/Bing 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛。