ReactPHP ускоряет PHPixie в 8 раз

    image
    ReactPHP это сокет сервер на PHP созданный для постоянной обработки запросов в отличии от стандартного подхода с Apache и Nginx где процесс умирает по окончании обработки одного запроса. Поскольку инициализация кода таким образом осуществляется только один раз то на отдельном запросе мы упускаем весь оверхед от загрузки классов, запуска фреймворка, считывания конфигурации итд.

    Ограничением тут является то, что программист должен помнить что процесс и все поднятые сервисы будут использованы множество раз и по этому доступ к глобальному или статическому скоупу не желателен. Это делает сложным использование ReactPHP с большинством фреймворков не созданных для такого подхода.

    К счастью PHPixie сама отказалась от глобального и статического скоупов, что позволяет легко запустить ее из-под ReactPHP.


    Для начала добавим его поддержку в проект:
    php ~/composer.phar require react/react
    


    Затем создаем файл react.php в корневой папке:
    <?php
    
    require_once('vendor/autoload.php');
    
    $host = 'localhost';
    $port = 1337;
    
    $framework = new Project\Framework();
    $framework->registerDebugHandlers(false, false);
    
    $app = function ($request, $response) use ($framework, $host, $port) {
        $http = $framework->builder()->components()->http();
    
        // Строим реальную URI запроса
        $uri = 'http://'.$host.':'.$port.$request->getPath();
        $uri = $http->messages()->uri($uri);
    
        // Приводим запрос к PSR-7
        $serverRequest = $http->messages()->serverRequest(
            $request->getHttpVersion(),
            $request->getHeaders(),
            '',
            $request->getMethod(),
            $uri,
            array(),
            $request->getQuery(),
            array(),
            array(),
            array()
        );
    
        // Передаем запрос в фреймворк
        $frameworkResponse = $framework
            ->processHttpServerRequest($serverRequest);
    
        // Вывод ответа
        $response->writeHead(
            $frameworkResponse->getStatusCode(),
            $frameworkResponse->getHeaders()
        );
        $response->end(
            $frameworkResponse->getBody()
        );
    };
    
    $loop = React\EventLoop\Factory::create();
    $socket = new React\Socket\Server($loop);
    $http = new React\Http\Server($socket);
    
    $http->on('request', $app);
    
    $socket->listen($port);
    $loop->run();
    


    Запускаем:
    php react.php
    


    Теперь перейдя по ссылке localhost:1337/ видим ту же PHPixie только запущенную как сервер. Простой бенчмарк на дефолтном контроллере показал увеличение производительности примерно в 8 раз, что не удивительно если взять во внимание то на сколько меньше кода выполняется при каждом запросе. Для тех кто захочет повторить мой эксперимент заметьте что я добился наилучшего результата с библиотекой event как бекенда для ReactPHP (он может работать и без нее, но только чуть медленнее получается).

    Правда сам ReactPHP вносит несколько ограничений. Во-первых вам все равно понадобится веб-сервер для статических файлов. Но самое грустное то, что из коробки он не поддерживает данные из тела запроса ($_POST), хотя существуют способы добиться этого.

    Наличие постоянного рантайма открывает интересные возможности, как например создание чата без надобности во внешней базе данных. Конечно пока-что это только эксперимент, но если идея приживется то PHPixie может получить отдельный компонент с более широкой поддержкой ReactPHP, включая например сессии и загрузку файлов.
    Share post

    Comments 42

      +3
      Проблема в том, что любая блокирующая операция (запрос в базу данных, file_get_contents итд.) заблокирует всех клиентов.

      Потому писать код в таком ключе нужно совсем по другому…
        +1
        И да, и не совсем.
        В обычном PHP-FPM у нас же тоже всё блокируется. Как решаем проблему? Создаем пул воркеров и распределяем нагрузку по ним. Вот только в случае с ReactPHP не приходится поднимать всю систему заново, классы уже загружены, некоторые неизменные объекты тоже можно использовать для разных запросов, именно это и дает основной прирост производительности — PHP перестает умирать после каждого запроса.
          0
          что мешает запустить n серверов одновременно и балансировать нагрузку с nginx
          +1
          Можно было бы использовать новую фишку uWSGI — phpsgi, вот только автор пока, похоже, отложил её в долгий ящик — не реализована как минимум обработка данных в POST-запросах.
          Кстати, встроенные возможности uWSGI (планировщик, спулер, мулы и т.д.) могли бы решить часть проблем с блокирующими операциями.
            +1
            обработка данных в POST запросах реализована, не реализована конкретно обработка multipart запросов, это несколько другая штука и в принципе ее можно добавить, готовые парсеры запросов существуют, единственнео что я под это дело все же отдельный бы воркер запилил.
            +2
            Работает быстрее зато данные из $_POST нужно тащить какими-то костылями, и всячески избегать синхронных операций и утечек памяти. ИМХО, так себе улучшение. Не более чем «поиграться и забыть». Не представляю себе это в продакшине.
              0
              Ну доя например рид-онли сайтов вполне может подойти думаю. Конечно в любой проект засунуть не получится
                +1
                Рид-онли можно и статику нагенерить и раздавать тем же nginx…
                  0
                  Ну зависимо от количества. Я уже видел статический магазин на 8 гигабайт файлов как следствие множества продуктов и страниц. Для таких имхо статиуа уже проблемно
                    +1
                    Почему проблемно? Varnish и вперед.
                +1
                данные из $_POST нужно тащить какими-то костылями

                не кастылями, а через абстракцию над SAPI (тот же PSR-7 предоставляет чудные интерфейсы для этого дела).

                и всячески избегать синхронных операций и утечек памяти

                Ну с утечками памяти да, хотя опять же можно просто иногда перезагружать воркеры. Что до «избегать сихронных операций» — я чуть ниже отписал.
                0
                Буквально вчера замерял то же на Yii 2.0 и пришёл к выводу о практической бесполезности использования этого дела в рамках фреймворка…
                  0
                  Случайно отослал коммент два раза
                    0
                    Поигрался еще и все действительно упирается в синхронний доступ к БД. Сейчас пробую php-pm
                      +1
                      Можно и асинхронный попробовать, в mysqli есть такая возможность
                    +4
                    Как уже отмечали люди выше, писать асинхронщину бывает больно, но если есть желание сделать что-то типа честного fastcgi и при этом не менять код то есть альтернативное решение на базе того же reactphp — php-pm. По сути это менеджер процессов который будет поднимать ваше приложение и ожидать запроса. После завершения обработки запроса можно либо перезагрузить воркер либо просто почистить его (например в случае с доктриной — отчистить UoW). Если вся наша система является stateless или легко сама себя чистит нам более не придется перезагружать воркеры и тратить серверное время на бутстрап приложения с инициализацией сервисов и т.д. Что до утилизации CPU — можно просто увеличить количество воркеров или сделать нормальный пайплайнинг — например отдельный коркер принимающий запросы и отдельные воркеры для их разбора.

                    В самом простом случае можно просто поднять RPS за счет устранения времени на бутстрап приложения (все же обычно не изветсно оставляют ли сервисы что-то после себя и ускорение за счет того что приложение будет висеть всегда уже нужно будет намного серьезнее тестить на всякие побочные эффекты).
                      0
                      Типа «пишем демона на PHP»? Ну ок, а зачем?
                      Производительность? Вы с чем конкретно сравнивали?
                      >в отличии от стандартного подхода с Apache и Nginx где процесс умирает по окончании обработки одного запроса
                      вроде как такие стандартные подходы остались в веселом прошлом?
                        0
                        Не остались ) фреймворки поднимаются с нуля при каждом запросе
                          0
                          А почему вы не любите чтото вроде apc? По сравнению с ним, я думаю не будет такого ускорения.
                            +1
                            apc (к слову нынче opcache, который еще с 5.5 по умолчанию в ядре пыха) это лишь опкод кэшер, он не избавляет нас от необходимости на каждый запрос инициализировать систему, заного подключаться к базе и т.д. Он всего-лишь избавляет от необходимости работы с файловой системой так как уже распаршенные опкоды висят в общей памяти. Opcache еще в довесок расширяет оптимизации дополнительно, например кэш строк хранит не для процесса а для всего пула процессов, чем экономит память существенно… Даже в этом случае инициализация приложения это какое-то время, которое для некоторых критично (скажем если у вас 100 милисекунд на запрос это порог, вы явно захотите убрать лишние 10-15 милисекунд инициализации системы.
                              0
                              В заголовке написано — ускоряет в 8 раз. Вот интересно было по сравнению с чем, с голым апачем и php как cgi? Ну так толку так сравнивать. Apc или что-то другое это уже тонкости.
                                0
                                Автор проверял hello world, пустой контроллер, никакой бизнес логики и т.д. То есть по сути измерения проводились между «разбор запроса + маршрутизация + время инициализация приложения» и «разбор запроса + маршрутизация без учета времени инициализации приложения».

                                То что автор не описал методику испытаний это уже само по себе попахивает маркетингом… но по сути без разницы с чем сравнивать, цифры будут примерно одинаковыми. Как никак если пых у нас был без opcache в одном случае то во втором это просто не играет роли так как весь код и так в памяти крутится.

                                  0
                                  >но по сути без разницы с чем сравнивать
                                  В том то и прикол, что если запустить олдскульно PHP как CGI на Апаче то разница возможно как раз и будет гдето в десять раз.
                                  Ну вот я и удивился — оно так нафига делать)
                                    0
                                    оно так нафига делать

                                    Давайте будем реалистами, скорее всего автор взял свой типовой стэк и на нем все гонял. Откуда взялась цифра «в 8 раз» я в принципе уже расписал — это ускорение за счет отсутствия времени инициализации фреймворка. Профит более чем очевидный, более того php-pm уже довольно часто используется для увеличения RPS на продакшене.
                          +2
                          вроде как такие стандартные подходы остались в веселом прошлом?

                          Нет, после завершения запроса процесс просто перезапускается и в случае с php-fpm нет расходов по времени на запуск самого PHP. А вот приложение как запускалось с нуля так и запускается на каждый запрос (разве что с opcache это дело сильно ускоряется). Подходы с php-pm позволяют нивелировать и эти накладные расходы и при этом продолжать писать сихронный код не заботясь о том что у нас там демоны.
                            +1
                            > (разве что с opcache это дело сильно ускоряется)
                            Я всякое подобное и имею ввиду, а есть еще apc, hhvm и тд и тп.
                            Вот лично я в чистом виде apache + php давно в серьезных проектах не видел.
                              0
                              в чистом виде apache + php давно в серьезных проектах не видел.

                              Ну потому и нет смысла об этом упоминать.

                              а есть еще apc, hhvm и тд и тп.

                              apc уже нет (только для php5.4 и менее), есть opcache + apcu разве что, hhvm это совсем другая штука, там ускорение опять же за счет того что у нас:
                              — код хранится в памяти и его не надо разбирать заного (мы этого можем добиться включив opcache и вырубив полностью инвалидацию кэша, хоть это имеет смысл делать только на больших нагрузках)
                              — JIT позволяет оптимизировать опкоды и генерить оптимизированный под текущую задачу машинный код. В PHP на данный момент для каждого опкода в каждый момент времени выполняется один и тот же машинный код (что логично), а HHVM в зависимости от контекста — разный, за счет этого мы получаем возможность оптимизаций. PHP7 подготовил неплохую почву для внедрения JIT в будущем, да и в рамках OpCache чуваки из Zend уже выложили их PoC реализацию (правда которая пока не особо работает и ждет своего часа).
                                0
                                >Ну потому и нет смысла об этом упоминать

                                Так я с чего и начал, с чем сравнивают то? А так то писать демона на PHP на нагрузках это не сильно здравая идея.
                                  +2
                                  As we can see, the react server with nginx as load balancer is over 15 times faster than old school PHP-FPM + APC.

                                  > marcjschmidt.de/blog/2014/02/08/php-high-performance.html
                                    0
                                    Это сильно зависит от задачи. Зачастую куда дешевле демонизировать существующий код, чем переписывать его на другом языке, тем более если надо будет поддерживать синхронно обе версии.
                              0
                              Как зачем? Чтобы увеличить производительность без смены используемого стакан технологий.
                              –2
                              Давно не следил за PHP, в нем уже перестала течь память и его больше не нужно перезапускать каждые N обработанных запросов?
                                +3
                                с версии 5.3 сборщик мусора начал поддерживать циклические ссылки, так что проблему могут составлять только кривые расширения. В целом же уже добрых лет 5 можно писать демоны на PHP.
                                –3
                                Проблема в том, что php создан, чтобы умирать. Так что более интересны долговременные эффекты использования этой штуки.
                                  0
                                  А если кто-то напишет статью в духе «php теперь может не умирать» вы ему тоже поверите наслово? У меня в продакшене крутится уже почти пол года апишка на reactphp (по сути архитектура такая же как и в php-pm но с минимальными отличиями), знаю людей у которых php-pm крутится, и так же никаких проблем.

                                  Проблемы с долгоиграющими PHP скриптами обычно вызваны:
                                  — руки из одного места
                                  — кривая обработка ошибок
                                  — использование стремных экстеншенов для PHP (стандартные штуки которые нужны для всего этого добра, типа libev/libeveb, libuv, pcntl проблем не вызывали) которые текут.

                                  В целом же проблем с php-pm нет вообще никаких, у нас в качестве демона только менеджер процессов и обработка запросов, а воркеры вы можете хоть после каждого запроса перезапускать.
                                    –1
                                    Не пробовали привязать jit? Должна получится интересная связка.
                                      0
                                      А при чём тут JIT?
                                        0
                                        Я про связку reactphp + jit
                                          +1
                                          JIT для PHP — это неудавшийся в плане прироста производительности эксперимент. master PHP7 даст больше. Посмотрите выступление Дмитрия Стогова с devconf.
                                            0
                                            ну не совсем неудавшийся, так как это вынудило создать phpng, какой-никакой а профит. То что валяется сейчас на гитхабах в теории должно ускорять всякие мелочи типа вызовов функций и т.д. просто до phpng накладные расходы на управление памятью были слишком большие (работа с кучей, большие кэш мисы и т.д), а после phpng уже просто не занимались JIT-ом, времени небыло. В перспектике JIT в совокупности с phpng даст еще больший профит, и кто знает, может кто-то возьмется пока чуваки из zend заняты.
                                              0
                                              Вот я про это и говорю. С данным подходом становится допустимо потратить один раз некоторое время(на самом деле довольно большое) на выполнение дополнительных оптимизаций.
                                                0
                                                какие дополнительные оптимизации?

                                Only users with full accounts can post comments. Log in, please.