問題描述

我們的團隊正在開發 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 輔助翻譯的英文資料結果。如果您對結果不滿意,可以加入我們改善翻譯效果:薇曉朵技術論壇。