Разница в производительности PHP-фактора 30 от Linux до Windows

Наша команда работает над созданием плагинов WordPress и предоставляет размещенные экземпляры на нескольких независимых серверах. Наша установка WordPress управляется Git, на всех серверах установлена одна и та же установка источника и WordPress, только домены и фактические данные в базе данных различаются. Для каждой установки MySql работает на одном хосте. WordPress работает исключительно на каждом сервере.

Однако после развертывания этой установки на Windows Server 2008 RC2 мы заметили резкую разницу в производительности по сравнению с нашими другими серверами: время генерации страницы увеличивается с avg. От 400 до 4000-5000мс для страниц, сгенерированных с помощью PHP. Для статических ресурсов, поставляемых только Apache, скорость примерно такая же, как у linux.

Поэтому мы предприняли некоторые шаги, чтобы сузить проблему:

  1. Убедитесь, что нет антивирусного программного обеспечения или других объектов домена Windows, мешающих
  2. Собирайте данные профилирования для идентификации таймеров во время выполнения сценария
  3. Тестирование различных серверных и аппаратных настроек
  4. Дважды проверьте конфигурацию Apache и PHP для очевидных ошибок конфигурации.

После некоторого профилирования мы быстро заметили, что оценка регулярных выражений на наших машинах Windows ужасно медленная. Оценка 10.000 Регулярных выражений (preg_match) занимает около 90 мс в Linux и 3000 мс в Windows.

Профилирование, системные тесты и детали конфигурации приведены ниже. Мы не хотим оптимизировать этот скрипт (который мы знаем, как это сделать). Мы хотим, чтобы сценарий работал примерно на той же скорости в окнах, что и на Linux (с той же настройкой относительно 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 на наших серверах (то же самое, что давало плохую производительность). Но сегодня они бегут невероятно быстро (~ 1сек/запрос). Добавление Opcache сводится к ~ 400ms/req. Apache/PHP/Windows остались прежними.

1) Результаты профилирования

Профилирование выполнялось с помощью XDebug на всех машинах. Обычно мы собрали всего несколько прогонов - этого было достаточно, чтобы выявить место, где большую часть времени (50% +) было потрачено: метод [get_browser][1] плагина WordPress wp-slimstats:

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 на запрос страницы). Это занимает около 90 мс в Linux и 3000 мс в Windows. Результаты были одинаковыми для всех тестируемых установок (на рисунке показаны данные двух исполнений):

wp_slimstat::_get_browser profiling results on IIS8

Конечно, загрузка двух огромных массивов занимает некоторое время. Оценка регулярных выражений. Но мы ожидаем, что они возьмут примерно то же самое время на Linux и Windows. Это результат профилирования в linux vm (только для одной страницы). Разница довольно очевидна:

enter image description here

Другим убийцей времени был 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 исполнения скриптов):

enter image description here

В linux:

enter image description here

Последним настоящим убийцей было время перевести. Каждый перевод, загруженный из памяти, занимает от 0.2 мс до 4 мс в WordPress: enter image description here

В linux:

enter image description here

2) Протестированные системы

Чтобы убедиться, что виртуализация или Apache действительно влияют на это, мы протестировали это на нескольких настройках. Антивирус был отключен во всех настройках:

  • Linux Debian, Apache 2 и PHP на современных стабильных выпусках. Это то же самое для разработчиков, работающих на своих виртуальных машинах, как для промежуточных/живых серверов. Действуя как эталонная система желаемой производительности. Либо запустите в нашем офисе, либо на каком-то хостинге (совместное пространство). У систем Windows было от 4 до 8 ГБ оперативной памяти, в то время как использование памяти составляло 50%. Виртуализации никогда не запускают Windows и Apache одновременно.
  • Life-Servers, работающие на T-Systems (управляемые виртуальные серверы), на 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: Запуск PHP в качестве модуля apache на Life-Servers был немного быстрее, чем метод fastcgi: до ~ 2 секунд, на 50% меньше.

Запуск памяти

Через некоторое время наш Live-сервер перестает работать, вызывая эти исключения из памяти:

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% свободной памяти (2GB+). Таким образом, на сервере фактически не хватает бара. Перезапуск Apache/PHP исправил эту проблему на данный момент.

Не уверен, что эта проблема связана с проблемами производительности здесь. Тем не менее, поскольку оба вопроса, по-видимому, связаны с памятью, он включен здесь. Особенно мы попытаемся воспроизвести настройки Windows-тестов, обеспечивающие достойную производительность.

3) Конфигурация Apache и PHP

... вероятно, не имеют общих ошибок. Буферизация вывода включена (по умолчанию), многобайтовое переопределение отключено,... Если какие-либо опции представляют интерес, мы с радостью их предоставим.

Вывод httpd.exe -V

Server version: Apache/2.4.7 (Win32)
Apache Lounge VC10 Server built:   Nov 26 2013 15:46:56
Server 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) Текущая подозреваемая причина

Старое предположение:

Все три примера сильно зависят от больших массивов и операций с строками. Что-то вроде обычной фабрики. Поскольку реализация работает нормально в Linux, мы подозреваем, что это проблема с памятью в Windows. Учитывая отсутствие взаимодействия с базами данных в местах с пин-точками, мы не подозреваем, что проблема с базой данных или сервером <-> PHP. Как-то взаимодействие с PHP-памятью кажется медленным. Может быть, кто-то мешает памяти на Windows сделать доступ значительно медленнее?

Старое предположение 2:

Поскольку один и тот же стек отлично работает на других машинах Windows, мы предполагаем, что проблема находится где-то в конфигурации Windows.

Новое предположение 3:

На самом деле, я не уверен в своих предположениях. Зачем запускать PHP намного медленнее, чем fastcgi, тогда как модуль apache>

Любые идеи о том, как проверить это или найти настоящую проблему здесь? Любая помощь или руководство для решения этой проблемы приветствуется.

Ответ 1

В Windows есть множество служб/политик, которые ограничивают, предотвращают, защищают, контролируют и т.д. Использование компьютера в любой ситуации.

Хороший специалист, сертифицированный Microsoft, сможет решить ваш вопрос в течение нескольких минут, потому что у них будет возможность точно определить, какие настройки/службы/политики проверять и отключать/включать/изменять настройки, чтобы скрипты PHP выполнялись быстрее.

Из моей памяти я могу только предложить вам проверить все, что касается ОЗУ, доступа к жесткому диску, переменных среды, ограничений и безопасности (например, брандмауэра). Все, что может повлиять на выполнение скрипта php, начиная с некоторых политик вызова удаленного управления и заканчивая памятью рабочего стека.

Логика заключается в том, что php.exe вызывает некоторый внешний.dll файл для выполнения какой-либо операции, могут быть проверки на пути, выполняемые ОС, что замедлит отправку запроса через такую DLL и получит от него ответ. Если.dll использует жесткий диск для доступа к чему-либо - политики доступа к жесткому диску входят в сцену. Кроме того, как все находится в памяти - в ОЗУ или жестком диске ОЗУ. Политика применения. Темы. Ограничения на максимальный процент, доступный для использования в приложениях.

Я не говорю, что хосты на базе Windows плохи, просто для них сложнее настроить для обычного администратора. Если у вас есть специалист Microsoft на руках, он может настроить ваш сервер так же быстро, как сервер на базе Linux.

Ответ 2

  • включить 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

  • включить Zend Opcode, когда на 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

  • отключите Wordpress расширения шаг за шагом, чтобы найти использование памяти монстра

  • set Wordpress: define('WP_MEMORY_LIMIT', '128M'); , если вы не используете плагины для конвертирования изображений, которые должны быть достаточными
  • установить неограниченную память в php.ini ini_set('memory_limit', -1);
  • профиль без запуска Xdebug, это звучит безумно, но сам отладчик имеет большое влияние
  • используйте memory_get_usage и распределите вызовы по всей системе, чтобы найти положение кода, где утечка памяти
  • дать zend.enable_gc=1 попробовать, скрипты будут медленнее, но использовать меньше памяти
  • возможно, просто отключите проверку браузера пользователя в настройках SlimStats.
  • если это невозможно, попробуйте переопределить функцию SlimStats getBrowser(), с более быстрой заменой getBrowser()
  • для сравнения скорости пользовательских агентов-получателей см. https://github.com/quentin389/ua-speed-tests
  • https://github.com/garetjax/phpbrowscap

Ответ 3

Я взглянул на этот плагин на Github:

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

И включенный файл-нарушитель - это файл, который в какой-то степени был минимизирован и действительно является данными (а не кодом), есть 5 вариантов, каждый из которых составляет около 400 КБ

Существует также файл maxmind.dat размером 400 КБ, хотя я не знаю, использует ли он оба.

Вы используете более старую версию плагина, версия 3.2.3, и есть намного более новая, которая может решить вашу проблему.

Сравнение различий сложно, потому что автор или тот, кто не сохранил историю git в порядке, поэтому мне пришлось вручную разбить файл. Большинство изменений, связанных с _get_browser, похоже, добавляют кэш.

Возможно, загрузка этого файла медленна для синтаксического анализа, но я ожидаю, что PHP будет загружать оба файла с одинаковой скоростью на обеих платформах, обеспечивающих работу кэширования IO.

EDIT Ищите немного ближе, что может не решить вашу проблему. Эти файлы представляют собой в основном большие таблицы поиска регулярных выражений. У вашей системы Linux был кеш APC, и этого нет? Кэш APC, вероятно, сохранит кеширование файлов PHP (хотя и не скомпилированные шаблоны регулярных выражений)

Ответ 4

Используйте NGINX и FCGI для PHP через сокет UNIX (не TCP-сокет).

http://wiki.nginx.org/PHPFcgiExample

Вы сразу заметите улучшения скорости даже без ускорителей. Кроме того, выше настройки будут иметь гораздо меньший объем использования памяти.

Ответ 5

Чтобы устранить проблему такого рода, вам необходимо:

  • Использовать системные журналы, а именно журналы ошибок: сортировка по уровню ошибок
    и/или дату для определения дат выпуска.
  • Сравните обновления Windows, используемые в любой из систем, один из них может быть неисправен.
  • Сравните программное обеспечение, используемое в любой системе, установка одного из них может быть неисправной.

В этом случае вы захотите удалить обновление Windows и/или программное обеспечение, вызывающее проблему, полностью завершить работу вашего сервера, а затем переустановить обновление или программное обеспечение (чтобы обеспечить стабильное состояние во время установки).

Инструменты, которые могут помочь вам устранить эту проблему, включают в себя пакет sysinternals: http://technet.microsoft.com/en-us/sysinternals/bb842062.aspx

Или, проще говоря, сценарии VBS с открытым кодом для создания сопоставимых списков обновлений и приложений в системе.