
Вообще говоря, я всегда делал бэкенд исключительно на PHP. Я считаю его отличным языком для бэкенда, вернее не только для бэкенда, а отличным языком. Некоторые не любят PHP за малый порог вхождения. Типа через этот малый порог вхождения входит очень много непрограммистов, которые потом пишут говнокод. Но я придерживаюсь мнения, что PHP никак не виноват в этом. Скорее наоборот. На PHP логика просыпается даже у тех, кто кроме говнокода ничего написать не может. Поэтому, в любом случае, это отличный язык программирования, который каждый год хоронят, но никак похоронить не могут. И в 2026 году он живее всех живых и показывает отличные результаты.
Вторым в списке для сравнения я решил поставить Дарт. Дело в том, что самое узкое место применения Дарта — это как раз-таки применение на серверах. Потому что в мобильной разработке он уже как стандарт, веб, фронтенд и десктоп мало-мальцевски он тоже подтягивает. Но вот на серверах пока что его используют очень мало. Одна из причин на то, что его мало используют на серверах, — это отсутствие библиотек, таких как, например, есть у PHP. Но, сейчас в эпоху LLM, это совсем не значит, что Дарт негоден на серверах. Как покажет сравнение, он очень-таки годен.
Третий кандидат для сравнения — это C++. Это его фреймворк Drogon, который в каком-то там 2014 или 2015 годах взлетел как самый быстрый фреймворк для серверов на бэкенде по скорости. Если честно, я ни разу не писал бэкенд на C++, поэтому это была первая попытка. Просто хотелось посмотреть, как C++ уничтожит двух других оппонентов со своей 100x производительностью.
Что сравнивалось
Полный существующий PHP API через PHP-FPM с OPcache и явно документированными исправлениями в одноразовых исследовательских копиях.
Независимый Dart API, скомпилированный AOT.
Независимый Drogon C++ API, сборка Release. Dart и Drogon не вызывают PHP и не перенаправляют запросы в PHP.
В нативных реализациях объявлены 127 методов app.php и реализованы семь операций ai.php; отдельно проверены HTTP upload/download, нативные push-воркеры и доставка открытым клиентам.
WebSocket-инфраструктура общая: отдельный Dart gateway для каждого варианта, обращающийся именно к соответствующему API.
Все операции замеров проходят через HTTP → nginx → реальный API → авторизацию/права → SQLite → сериализацию ответа.
Я использовал в основном то, что реально попадается на практике: отправка сообщений, хеширование, авторизация, получение блокировки записи, запись, чтение, удаление, обновление в SQLite. Код в этой публикации выложить нет возможности, возможно обновлю статью позже, добавив исходники.
Условия и воспроизводимость
Сервер: Англия/Лондон, Ubuntu 24.04, 2 vCPU AMD EPYC 9354P, 8 ГБ RAM.
PHP 8.4.22; Dart 3.13.0 linux_x64; Drogon 1.8.7; JsonCPP 1.9.5; ICU 74.2; nginx 1.30.3.
Во всех трёх API через SQL-запросы проверены SQLite 3.51.3, WAL, synchronous=FULL, foreign_keys=ON, busy_timeout=5000, page_size=4096.
У каждого варианта независимая одинаковая синтетическая база: 1000 строк тестовой сущности, 100 исходных сообщений, одинаковые роли, пользователи, индексы и разрешения. Новая копия базы для каждого числа процессов и повторения.
Все рабочие процессы исполнялись от одного пользователя.
Использован одинаковый приватный nginx, HTTP/1.1 с повторным использованием соединений, без gzip и TLS в серверном тесте.Основной прогон: 1 и 2 процесса, 3 повторения, 200 последовательных запросов на операцию в каждом повторении. В таблицах ниже по 600 исходных наблюдений на ячейку.
Основная нагрузка: 100/300/600 запросов/с, по 8 секунд; стресс: 1000/1500/2000 запросов/с, по 5 секунд, 2 процесса, 2 повторения; дополнительная проверка: 8 процессов, 1000/2000 запросов/с, по 3 секунды, 2 повторения.
Смесь: 30% история, 20% роли, 10% список чатов, 20% пагинация, 20% отправка сообщения. Сохранение контента измерялось отдельно. У отправляемых сообщений отключены внешние уведомления, но сохраняются полноценная запись, события и сигнал Redis.
Генератор посылает запланированный поток с пределом 32 одновременных запросов. Время считается от запланированного момента поступления, включая очередь генератора. Ошибки и завершившиеся запросы учитываются отдельно.
Процентили объединены из сырых выборок; это не среднее значение процентилей разных запусков. Исходные файлы и SHA256 перечислены в JSON-сводках.
Ограничение стенда: генератор нагрузки, прокси и рабочий проект используют те же 2 vCPU. Например, при 600 запросах/с генератор тратил около 0,60 мс CPU на запрос. Его CPU и память измерены отдельно. Поэтому значения на перегрузке — наблюдаемая производительность всей этой конфигурации, а не универсальный предел языка или чистый максимум API на выделенном железе.
Тёплые запросы, один рабочий процесс
В каждой ячейке p50 / p95, мс. Меньше — лучше.
Операция |
PHP-FPM |
Dart AOT |
Drogon C++ |
|---|---|---|---|
Снимок ролей и разрешений |
2.17 / 2.95 |
1.01 / 2.02 |
0.96 / 1.45 |
История: 80 сообщений |
2.72 / 4.16 |
3.33 / 5.12 |
3.76 / 4.64 |
Список чатов |
2.13 / 2.91 |
0.93 / 1.87 |
0.91 / 1.42 |
Пагинация: 40 строк |
2.18 / 3.17 |
1.20 / 2.33 |
1.22 / 1.59 |
Сохранение сообщения |
4.99 / 7.66 |
2.14 / 4.36 |
1.85 / 3.25 |
Сохранение документа |
2.35 / 3.60 |
0.97 / 1.86 |
0.98 / 1.36 |
Для отправки сообщения Drogon уменьшил медиану относительно PHP примерно на 63%, Dart — на 57%. Для истории PHP опередил обе текущие нативные реализации. Это разница конкретного кода: наличие C++ само по себе не гарантирует более быстрый результат.
Смешанная нагрузка 600 запросов/с, два процесса
API |
Обработано, запросов/с |
p50, мс |
p95, мс |
p99, мс |
CPU API, мс/запрос |
Память PSS, МиБ |
|---|---|---|---|---|---|---|
PHP-FPM |
524.38 |
236.76 |
879.42 |
1 207.94 |
1.97 |
30.91 |
Dart AOT |
599.79 |
3.42 |
31.76 |
167.65 |
1.14 |
31.08 |
Drogon C++ |
599.92 |
3.59 |
8.19 |
11.99 |
1.32 |
12.17 |
Dart и Drogon выдержали входящий поток 600 запросов/с в этом тесте; PHP с двумя процессами не успевал. Ноль ошибок у PHP здесь означает, что очередь в конце дождалась обработки, а не что задержка была приемлемой. При одном процессе PHP обработал около 377 запросов/с на таком же входящем потоке; p95 достигал 4,58 секунды.
PSS — сумма пропорционально разделённой памяти процессов API, включая мастер PHP-FPM. Она не считает общие страницы целиком по нескольку раз, как сумма RSS. Это наблюдение после серии, не непрерывно измеренный пик. Общий nginx, генератор, Redis, WebSocket gateway и push-воркеры в этой колонке не смешаны с памятью API.
Перегрузка, два процесса:
Входящий поток |
API |
Обработано, запросов/с |
p50, мс |
p95, мс |
PSS, МиБ |
|---|---|---|---|---|---|
1000 |
PHP-FPM |
560.94 |
1 971.21 |
3 853.38 |
31.37 |
1000 |
Dart AOT |
996.98 |
10.86 |
107.44 |
33.50 |
1000 |
Drogon C++ |
973.27 |
54.82 |
128.49 |
12.64 |
1500 |
PHP-FPM |
531.80 |
4 715.65 |
8 617.77 |
31.36 |
1500 |
Dart AOT |
1 051.33 |
1 090.88 |
2 045.36 |
34.59 |
1500 |
Drogon C++ |
922.35 |
1 534.06 |
2 934.37 |
12.70 |
2000 |
PHP-FPM |
509.15 |
6 633.42 |
13 945.13 |
31.37 |
2000 |
Dart AOT |
1 096.23 |
2 052.18 |
3 951.29 |
36.22 |
2000 |
Drogon C++ |
919.18 |
2 812.01 |
5 562.40 |
12.73 |
При 2000 входящих запросах/с все варианты перегружены. Dart обработал примерно в 2,15 раза больше запросов, чем PHP с теми же двумя процессами; Drogon — примерно в 1,81 раза больше. Это не режим, который стоит принимать за нормальную эксплуатацию: задержки уже измеряются секундами, в реальных проектах при таком - нужно думать над оптимизацией или апгрейдить железо.
Проверка пула из восьми процессов
API |
Обработано при входящих 1000/с |
Обработано при входящих 2000/с |
PSS при 2000/с, МиБ |
|---|---|---|---|
PHP-FPM |
721.33 |
709.72 |
46.77 |
Dart AOT |
931.36 |
927.14 |
117.60 |
Drogon C++ |
950.59 |
798.98 |
34.08 |
Больше процессов помогло PHP: на перегрузке получено около 710–721 запросов/с вместо 509–561 при двух процессах. Поэтому было бы неправильно называть 509 запросов/с пределом PHP. Dart и Drogon от восьми процессов на этих двух vCPU не выиграли; память выросла. Среди проверенных конфигураций лучше выглядит небольшой пул нативных процессов и более широкий PHP-FPM-пул. Для назначения production SLO нужны более длительные прогоны с внешним генератором и реальным распределением данных.
Разбор времени отправки сообщения
Медианы внутренних таймеров при одном процессе, мс:
Этап |
PHP-FPM |
Dart AOT |
Drogon C++ |
|---|---|---|---|
Авторизация |
0.23 |
0.09 |
0.10 |
Получение блокировки записи |
0.01 |
0.01 |
0.01 |
SQL-записи |
0.24 |
0.10 |
0.11 |
Коммит |
1.54 |
0.92 |
0.86 |
Сериализация ответа |
0.00 |
0.00 |
0.01 |
Внутренний total |
3.30 |
1.33 |
1.20 |
Таймеры вложены друг в друга, и их медианы нельзя складывать для получения общей медианы. Внешнее время HTTP включает также прокси, планирование процессов и передачу ответа. В PHP отправка уведомления Redis измерялась отдельно: медиана около 0,25 мс. В нативных API публикация асинхронная.
Коммит остаётся заметной частью отправки: около 0,86–1,54 мс в этих сериях. Переписывание языка не убирает ожидание надёжной записи SQLite. На чтении истории у Dart/Drogon дороже текущая обработка и сериализация: внутренний dispatch примерно 1,46/2,00 мс против 0,64 у PHP; encode примерно 0,42/0,37 против 0,04 мс. Именно этот путь заслуживает следующего профилирования нативных реализаций.
Также я отдельно тестил быстродействие модуля чата, хотелось проследить за каждым промежутком времени в процессах от нажатия кнопки отправки сообщения до получения в интерфейсе галочки "отправлено". Об этом ниже.
От нажатия «отправить» до кадра с галочкой
По 30 измеренных сообщений на вариант после трёх прогревочных; отправка через настоящий WSS, интервал между сценариями 400 мс после подтверждения. Все 90 сообщений подтверждены; ошибок Flutter в журнале нет. Время отсчитывается внутри обработчика, поэтому запуск CLI и доставка команды автоматизации не включены.
Backend |
До кадра с галочкой p50, мс |
p95, мс |
Диапазон, мс |
WSS внутри клиента p50, мс |
|---|---|---|---|---|
PHP-FPM |
117.50 |
126.22 |
109.10–126.93 |
105.71 |
Dart AOT |
120.70 |
136.97 |
108.17–138.27 |
103.93 |
Drogon C++ |
117.20 |
123.44 |
109.22–125.66 |
103.05 |
В этом коротком живом сценарии переход с PHP на Dart/Drogon не дал заметного ускорения галочки. Небольшие различия медиан сравнимы с сетевыми колебаниями и ожиданием следующего кадра. У Dart медиана кадра выше PHP, хотя медиана клиентского WSS ниже.
Тёплые внешние HTTPS запросы
Финальный сетевой прогон: 40 измерений на каждую из шести операций для каждого API; всего 720 запросов. По два прогревочных запроса исключены. Значения p50 / p95, мс:
Операция |
PHP-FPM |
Dart AOT |
Drogon C++ |
|---|---|---|---|
Роли |
102.97 / 105.42 |
101.66 / 103.81 |
101.89 / 102.73 |
История 80 сообщений |
103.72 / 117.38 |
105.81 / 111.16 |
103.93 / 105.12 |
Список чатов |
102.83 / 103.97 |
101.82 / 103.61 |
101.78 / 103.31 |
Пагинация 40 строк |
102.97 / 104.78 |
101.66 / 103.34 |
102.90 / 106.42 |
Отправка сообщения |
104.47 / 105.36 |
102.60 / 104.57 |
103.10 / 104.40 |
Сохранение контента |
102.54 / 103.08 |
101.70 / 102.72 |
101.81 / 103.00 |
Подтверждение и доставка второму открытому клиенту по WebSocket
Отдельно 60 сообщений на вариант после двух прогревочных; ACK — подтверждение отправителю; доставка — получение соответствующего сообщения вторым сокетом. Это транспортные измерения, без Flutter. Ошибок и таймаутов в завершённом прогоне нет.
Backend |
ACK p50, мс |
ACK p95, мс |
Доставка p50, мс |
Доставка p95, мс |
Доставка p99, мс |
|---|---|---|---|---|---|
PHP-FPM |
103.75 |
105.96 |
105.19 |
106.86 |
112.38 |
Dart AOT |
108.74 |
111.58 |
109.24 |
111.68 |
120.81 |
Drogon C++ |
102.47 |
103.84 |
103.07 |
104.17 |
105.77 |
Установка TCP-соединения: медиана 99.60 мс, 12 проб. TCP вместе с TLS: 203.18 мс, 6 проб. Это два разных измерения; второе включает первое. Все тёплые результаты выше используют уже установленные соединения. TCP connect показывает масштаб сетевой составляющей около 100 мс, т е по сути большая часть времени уходила на сетевой транспорт, а не на обработку на бэкенде, поэтому особого толку от замены PHP на более высокопроизводительные языки на малых нагрузках - просто нет. Тут вспомнились Дуровы, Цукерберги, киткаты и HPHPHPHP.
Вывод
Для простой настройки сервера и простого поиска специалиста, который сможет быстро настроить бэкенд, при этом довольно близкого уровня производительности - в лидерах, конечно, PHP. Для высокой пропускной способности текущей смешанной нагрузки предпочтительнее Dart AOT. Dart оказался тем самым серединным путем по скорости близок к Drogon, по потреблению памяти к PHP, поэтому свой выбор остановлю на нем. Для минимальной памяти и максимальных нагрузкок — Drogon C++. Выбор между заменой стека и покупкой железа лежит на пользователе, но при текущих ценах на память, C++ может реально сэкономить бюджет.
При сохранении PHP существенное улучшение под этой нагрузкой уже даёт правильно выбранный пул PHP-FPM, хотя в проверенных конфигурациях он уступает нативным вариантам по смешанной пропускной способности. Переводить всё на C++ только из предположения «C++ всегда быстрее» эти данные не поддерживают. 100x я не получил и 10x тоже (возможно, я не знаю каких-то секретов оптимизации C++ и попрошу тех, кто знает - написать подобную статью, с удовольствием прочту), это еще раз показывает, что современные языки программирования в большинстве случаев не настолько тормознутые и выбор C++ только ради увеличения производительности - далеко не всегда себя оправдывает.
Комментарии (7)

andreymal
17.09.2026 12:59В 2026 году без Rust несерьёзно https://www.techempower.com/benchmarks/#section=data-r23&test=query

ToxaBes
17.09.2026 12:59Эм, логичнее было бы сравнивать PHP с нодой, гошкой и растом, причем свежий PHP-FPM версии 8.5+ и фреймворком грамотно реализующим PSR стандарты, например Slim 4+.

csharpminor0
17.09.2026 12:59логичнее было бы сравнивать PHP с нодой, гошкой и растом
этих сравнений валом, а сравнение в статье - редкость, в чем и ценность.
8.5 не сильно от 8.4 по производительности отличается и сейчас в основном 8.4 распространена везде, а PSR на производительность не влияет, т е ваши возмущения по сути высосаны из пальца, а статья хорошая

Arenoros
17.09.2026 12:59возможно, я не знаю каких-то секретов оптимизации C++ и попрошу тех, кто знает - написать подобную статью
1. чего их писать их и так тысячи, хоть в формате видео https://www.youtube.com/watch?v=2KON1UPZEIc хоть в книгах "C++ High Performance"
2. о какой оптимизации может идти речь без профилирования?
мощь C++ в практически безгранмичной возможности оптимизировать узкие места, там где у дригих языков способы оптимизации заканчиваются в С++ они только начинаются

TimurZhoraev
17.09.2026 12:59Сейчас скорее проблема не бэкэнда как такового а спецификаций, которые не отражают текущие потребности. С одной стороны давлеющее легаси из 90-х с другой микросервисы, распределённые архитектуры, асинхронность и кроссплатформ. Поэтому тут язык уже не важен, главное чтобы он (вместе с фреймворком) мог обернуть необходимые протоколы без лишней рутины, ну и конечно же если есть для этого скиллы, MCP или даже файн-тюн модели то это несомненно перевесит любые недостатки языка.
vkomp
Я возмущён! Как можно сравнивать бэкенд в отсутствие Go?! ;)
lil_master Автор
Go и ноду попробую в следующий раз)