company_banner

ВКонтакте снова выкладывает KPHP

    Привет! Сейчас будет дежавю.

    Мы снова выложили на GitHub наш PHP-компилятор — KPHP. Он проделал большой путь, и чтобы рассказать о нём, сначала телепортируемся на шесть лет назад.

    Поясню для тех, кто не в теме: платформа ВКонтакте изначально была написана на PHP. Со временем нас перестала устраивать производительность, и мы решили ускорить VK. Сделали компилятор — KPHP, который поддерживал узкое подмножество PHP. Это было давно, и с тех пор мы о нём не рассказывали, так как KPHP почти не развивался до 2018-го года.

    Но два года назад мы взялись за него, чтобы вдохнуть в эту разработку новую жизнь. Что сделали и какой получили результат — расскажу в этой статье. Она будет не о громком релизе, который можно прямо сейчас внедрять в свои проекты, а о внутренней разработке ВКонтакте, которую мы показываем сообществу и продолжаем развивать. Представлюсь: меня зовут Александр Кирсанов, я руковожу командой Backend-оптимизаций.

    А теперь — телепортация.

    delorean

    Из 2020-го в 2014-й


    Идёт 2014 год, и ВКонтакте опенсорсит репозиторий kphp-kdb. Наверняка многие из вас помнят этот момент.

    Там было много движков от VK, а также первая версия KPHP. Тогда многие заинтересовались и пошли смотреть, но… Но. На тот момент там не было подробной инструкции по сборке, а также документации и сравнения с аналогами. Не было канала связи с разработчиками и поддержкой. Дальнейших апдейтов на GitHub также не случилось. И всё равно некоторые энтузиасты пробовали пользоваться этими инструментами — возможно, кому-то даже удалось, но доподлинно не известно.

    Что касается KPHP — на тот момент он поддерживал версию PHP… даже не знаю. Что-то среднее между 4 и 5. Были функции, примитивы, строки и массивы. Но не было классов и ООП, не было современных (на момент 2014-го) паттернов разработки. И весь бэкенд-код ВКонтакте был написан в процедурном стиле, на ассоциативных массивах.

    В общем, инфоповод был интересным, но во что-то большее не развился.


    Из 2014-го в 2020-й


    Сейчас, в конце 2020 года, весь бэкенд-код ВКонтакте по-прежнему на PHP. Пара сотен разработчиков, миллионы строк.

    Но наш нынешний PHP-код мало чем отличается от актуального в индустрии. Мы пишем на современном PHP: у нас есть классы, интерфейсы и наследование; есть лямбды, трейты и Composer; а ещё строгая типизация, анализ PHPDoc и интеграция с IDE. И KPHP делает всё это быстрым.

    В 2014 году в техническую команду пришли новые люди, которые начали заниматься движками. (Да, ВКонтакте до сих пор десятки собственных закрытых движков, хотя точечно мы используем ClickHouse и другие известные.) Но KPHP долго никто не трогал. До 2018-го бэкенд-код действительно был на уровне PHP 4.5, а IDE плохо понимала код и почти не подсказывала при рефакторинге.

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

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


    Давайте про KPHP: что это и как работает


    KPHP берёт PHP-код и превращает его в С++, а уже этот С++ потом компилирует.

    Эта часть — техническая, она большая. Мы заглянем внутрь и увидим, что происходит с PHP-кодом: как из него получается С++ и что следует за этим. Не слишком кратко, чтобы обозначить базовые вещи, но и не чересчур детально — в дебри не полезем.


    KPHP выводит типы переменных


    В PHP любая переменная — это ZVAL, то есть «что угодно». В переменную можно записать число, объект, замыкание; в хеш-таблицу — добавить одновременно числа и объекты; а в функцию — передать любое значение, а потом это разрулить в рантайме.

    Если бы KPHP пошёл по такому пути, он бы не смог стать быстрым. Залог скорости — прежде всего в типизации. KPHP заставляет думать о типах, даже когда вы их явно не указываете.

    В PHP мы не пишем типы переменных (за исключением редких type hint для аргументов) — поэтому KPHP сам выводит типы. То есть придумывает, как бы объявить переменную в C++, чтобы это было лучше.

    Давайте посмотрим сниппеты на примерах.

    Пример:

    // PHP
    $a = 8 * 9;
    

    // C++
    int64_t v$a = 0;
    v$a = 8 * 9;
    

    Тут KPHP понял, что $a — это целое число, то есть int64_t в C++, и сгенерил такой код.

    Ещё пример:

    // PHP
    $a = 8 / 9;
    

    // C++
    double v$a = 0;
    v$a = divide(8, 9);
    

    Казалось бы, просто изменили умножение на деление — но уже другое. Деление в PHP работает не так, как в C++. 8/9 в С++ будет целочисленный 0, поэтому есть функция divide() с разными перегрузками. В частности, для двух интов она выполняет сначала каст к double.

    Следующий пример:

    // PHP
    function demo($val) { ... }
    // в других местах кода
    demo(1);
    demo(10.5);
    

    // C++
    void f$demo(double v$val) { … }
    

    KPHP проанализировал все вызовы функции demo() и увидел, что она вызывается только с целыми и дробными числами. Значит, её аргумент — это double. Перегрузки нет в PHP, нет её и в KPHP (и не может быть, пока типы выводятся, а не указываются явно). Кстати, если внутри demo() будет вызов is_int($val), то на аргументе 1 это будет true в PHP, но false в KPHP, так как 1 скастится к 1.0. Ну и ладно, просто не надо так писать. Во многих случаях, если KPHP видит, что поведение может отличаться, выдаёт ошибку компиляции.

    Дальше:

    // PHP
    $a = [1];
    $a[] = 2;
    

    // C++
    array < int64_t > v$a;
    v$a = v$const_array$us82309jfd;
    v$a.push_back(2);
    

    Здесь KPHP понял, что $a — это массив и в нём могут быть только целые числа. Значит, array<int64_t>. В данном случае array<T> — это кастомная реализация PHP-массивов, которая ведёт себя идентично. В PHP массивы могут быть и векторами, и хеш-таблицами. Они передаются по значению, но для экономии используют copy-on-write. Индексация числами и числовыми строками — это (почти) одно и то же. Всё это в KPHP реализовано похожим образом, чтобы работало одинаково.

    Ещё пример:

    // PHP
    $group = [
      'id' => 5,
      'name' => "Code mode"
    ];
    

    // C++
    array < mixed > v$group;
    v$group = v$const_array$usk6r3l12e;
    

    В этом массиве (в хеш-таблице) мы смешиваем числа и строки. В KPHP есть специальный тип mixed, обозначающий «какой-нибудь примитив». Это напоминает ZVAL в PHP, однако mixed — это всего лишь 16 байт (enum type + char[8] storage). В mixed можно сложить числа и строки, но нельзя — объекты и более сложные типы. В общем, это не ZVAL, а что-то промежуточное.

    Например, json_decode($arg, true) возвращает mixed, так как значение неизвестно на этапе компиляции. Или даже microtime() возвращает mixed, потому что microtime(true) — это float, а microtime(false) — массив (и кто это только придумал?..).

    И последний пример:

    // PHP
    $func_name = 'action_' . $_GET['act'];
    call_user_func($func_name);
    

    А здесь мы получим Compilation error. Потому что нельзя вызывать функции по имени — нельзя и всё. Нельзя обращаться по имени к переменным, к свойствам класса — KPHP напишет ошибку, несмотря на то что это работает в PHP.


    KPHP хоть и выводит типы, но позволяет их контролировать


    Выше мы видели: когда разработчик типы не пишет, они выводятся автоматом.
    Но их можно писать — с помощью PHPDoc @var/@param/@return или через PHP 7 type hint. Тогда KPHP сначала всё выведет, а потом проверит.

    Пример:

    /** @param int[] $arr */
    function demo(int $x, array $arr) { ... }
    
    demo('234', []);  // ошибка в 1-м аргументе
    demo(234, [3.5]); // ошибка во 2-м аргументе
    

    Ещё пример:

    /** @var int[] */
    $ids = [1,2,3];
    /* ... */
    // ошибка, если $group — это mixed[] из примера выше
    $ids[] = $group['id'];  
    // а вот так ок
    $ids[] = (int)$group['id'];
    

    Ручной контроль позволяет избегать непреднамеренных ухудшений типов. Без @var переменная $ids вывелась бы как mixed[], и никто бы этого не заметил. А когда разработчик пишет PHPDoc — значит, всё скомпилированное вывелось так же, как написано.


    KPHP превращает PHP class в C++ struct


    // PHP
    class Demo {
      /** @var int */
      public $a = 20;
      /** @var string|false */
      protected $name = false;
    }
    

    // C++
    struct C$Demo : public refcountable_php_classes<C$Demo> {
      int64_t v$a{20L};
      Optional < string > v$name{false};
    
      const char *get_class() const noexcept;
      int get_hash() const noexcept;
    };
    

    Если в обычном PHP классы — это более-менее те же хеш-таблицы, то в KPHP не так. На выходе получаются обычные плюсовые структуры, которые ведут себя ссылочно, как и в PHP (очень похоже на std::shared_ptr идеологически).

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

    Наследование — плюсовое (за исключением late static binding, но оно разруливается на этапе компиляции). Интерфейсы — это тоже плюсовое множественное наследование, там главное — refcount запрятать куда нужно. Правда, методы классов — это отдельные функции, принимающие this явно, так оно логичнее с нескольких позиций.

    Это же значит, что у KPHP-классов много ограничений. Например, нельзя обращаться к полям по имени или вызывать так методы. Нет и не может быть магических методов. Классы совсем никак не стыкуются с mixed. Нельзя из функции вернуть «либо класс, либо массив» — не сойдётся по типам. Нельзя в функцию передать разные классы без общего предка (впрочем, в KPHP есть шаблонные функции, но это уже сложнее). Нельзя в хеш-таблицу сложить одновременно числа, строки и инстансы — нет, иди и делай типизированный класс или используй именованные кортежи.

    В общем, когда разработчики пишут код, они всегда думают о типах и об их сходимости. Глупо ожидать, что напишешь фигню, а она заработает. Если ты следуешь ограничениям, получаешь скорость — иначе не бывает.


    Как конкретно происходит конвертация PHP в C++


    php to cpp

    Многие знакомы с этой терминологией — те, кто занимался языками, или компиляторами, или статическим анализом.

    Сначала PHP-файл превращается в линейный список токенов. Это такие минимальные неразрывные лексемы языка.

    Потом линейный набор токенов превращается в синтаксическое дерево (abstract syntax tree). Оно согласовано с приоритетами операций и соответствует семантике языка. После этого этапа есть AST для всех достижимых функций.

    Далее выстраивается control flow graph — это связывание функций и получение высокоуровневой информации о том, откуда и куда может доходить управление. Например, try/catch и if/else синтаксически похожи, но изнутри try можно добраться до внутренностей catch, а из if до тела else — нет. На выходе получается информация о соответствии вершин и переменных, какие из них используются на чтение, а какие на запись, и тому подобное.

    Потом происходит type inferring. Это тот магический вывод типов, который ставит в соответствие всем PHP-переменным — переменные С++ с явно проставленными типами, а также определяет возвращаемые значения функций, поля классов и другое. Этот этап согласуется с тем, как код впоследствии будет исполняться на С++, какие там есть функции-хелперы, их перегрузки и прочее.

    Имея типы, можно провести ряд оптимизаций времени компиляции. Например, заранее вынести константы, безопасно заинлайнить простые функции, а нетривиальные аргументы только для чтения передавать по const-ссылке, чтобы не вызывать конструктор копирования и не флапать рефкаунтер лишний раз.

    И наконец, кодогенерация: все PHP-функции превращаются в С++ функции, а PHP-классы — в С++ структуры. Изменённые файлы и их зависимости перезаписываются, и код проекта на С++ готов.


    Что дальше происходит с С++ кодом


    cpp to deploy

    Сгенерировать С++ из PHP — этого мало. Собственно говоря, это самое простое.

    Во-первых, в PHP мы используем кучу функций стандартной библиотеки: header(), mb_strlen(), curl_init(), array_merge(). Их тысячи — и все должны быть реализованы внутри KPHP с учётом типизации и работать так же, как в PHP. Реализация всего PHP stdlib (а также KPHP-дополнений), всех PHP-типов с операциями и допущениями — это называется runtime, вон там квадратик сверху.

    Во-вторых, PHP-сайт — это веб-сервер. Следовательно, и в KPHP должна быть вся серверная часть, чтобы можно было в том же nginx подменить PHP-шный upstream на KPHP-шный — и всё продолжало работать так же. KPHP поднимает свой веб-сервер, оркестрирует процессы, заполняет суперглобалы и переинициализирует состояние, как и PHP… Это тоже хардкорная часть — называется server, квадратик снизу.

    И только имея результирующий код C++, написанные runtime и server, всё это можно объединить и отдать на откуп плюсовым компиляторам. Мы используем g++ — там в диаграмме есть квадратик g++. Но не совсем так: у vk.com настолько огромная кодовая база, что этот компилятор не справляется, и поэтому мы применяем патченный distcc для параллельной компиляции на множестве агентов. В итоге всё линкуется в один огромный бинарник (это весь vk.com), он раскидывается на кучу бэкендов и синхронно перезапускается. Каждая копия запускает мастер-процесс, который порождает группу однопоточных воркеров. Вот они на самом деле и исполняют исходный PHP-код.

    Многие технические проблемы остаются за кадром — их не опишешь в статье на Хабре. Чего стоит один только сбор трейсов при ошибках: ведь в С++ не получить человекочитаемый стек, а хочется разработчику вообще его на PHP-код намаппить. Гигантское количество внутренних нюансов, множество подпорок и легаси — но в итоге продукт хорошо работает и развивается.


    KPHP vs PHP: что мы не поддерживаем


    По итогам предыдущей части статьи должно было сложиться чёткое понимание: KPHP не может взять любой PHP-код и ускорить его. Так не работает.
    Если код работает на PHP — это не значит, что он заработает на KPHP.
    KPHP — это отдельный язык, со своими ограничениями и правилами.

    1. KPHP не компилирует то, что принципиально не компилируемо. Например, выше мы говорили про вызов функции по имени. Туда же — eval, mocks, reflection. PHP extensions тоже не поддерживаются, так как внутренности KPHP пересекаются с Zend API примерно на 0%. Так что PHPUnit запустить на KPHP не выйдет. Но и не нужно! Потому что мы пишем на PHP, мы тестируем на PHP, а KPHP — для продакшена.
    2. KPHP не компилирует то, что не вписывается в систему типов. Нельзя в массив сложить числа и объекты. Нельзя накидать рандомных интерфейсов с лямбдами и разгрести это в рантайме. В KPHP нет волшебного типа any.
    3. KPHP не поддерживает то, что нам в VK никогда не было нужно. ВКонтакте куча своих движков — и мы с ними общаемся по специальному протоколу, который описан в TL-схеме. Поэтому нам никогда не нужна была человеческая поддержка MySQL, Postgres, Redis и прочего.
    4. Часть PHP-синтаксиса просто ещё не покрыта. Текущий уровень поддержки находится примерно на уровне PHP 7.2. Но отдельных синтаксических вещей нет: что-то сделать очень сложно, до другого не дошли руки, а оставшееся мы считаем ненужным. Например, KPHP не поддерживает генераторы и наследование исключений — мы не любим исключения. Ссылки поддержаны только внутри foreach и в аргументах функций. Всё так, потому что мы разрабатывали KPHP как удобный инструмент для наших задач, — а компилировать сторонние библиотеки в планы не входило.

    KPHP vs PHP: в чём мы превосходим


    В скорости. Если использовать KPHP грамотно, то код будет работать значительно быстрее, чем на PHP 7.4. А некоторых вещей нет в PHP — и чтобы при разработке он не падал с ошибками, там просто заглушки.

    Итак, в чём наш профит:

    • строгая типизация, примитивы на стеке, а классы — это не хеш-таблицы;
    • асинхронность (аналог корутин);
    • шаренная память для всех воркеров одновременно, живущая между исполнениями скрипта;
    • оптимизации времени компиляции;
    • оптимизации времени рантайма, чаще всего при чистых типах.

    Отдельно чуть-чуть расскажу про асинхронность. Это чем-то похоже на async/await в других языках, а чем-то — на горутины. KPHP-воркеры однопоточные, но умеют свитчиться между ветками исполнения: когда одна ветка ждёт ответ от движка, вторая выполняет свою работу, и когда первая дождалась — управление снова переключается туда.

    Например, нам нужно загрузить пользователя и одновременно посчитать какую-то подпись запроса (CPU-работа — допустим, это долго). В обычном (синхронном) варианте это выглядит так:

    $user = loadUser($id);
    $hash = calcHash($_GET);
    

    Но эти действия независимы — пока грузится пользователь, можно считать хеш, а потом дождаться загрузки. В асинхронном варианте это происходит так:

    $user_future = fork(loadUser($id));
    $hash = calcHash($_GET);
    $user = wait($user_future);
    

    То есть отличие от паттерна async/await в том, что мы никак не меняем сигнатуру функции loadUser() и всех вложенных. Просто вызываем функцию через конструкцию fork(), и она становится прерываемой. Возвращается future<T>, и потом можно подождать результат через wait(). При этом в PHP отдельно реализованы PHP-функции fork и wait, которые почти ничего не делают.

    В итоге: с одной стороны, мы следим за типами. С другой, можем делать запросы к движкам параллельно. С третьей, zero-cost abstractions (плохой термин, но пусть) — константы напрямую инлайнятся, всякие простые геттеры и сеттеры тоже, и оверхед от абстракций в разы меньше, чем в PHP.

    Если говорить про бенчмарки, то на средних VK-страничках у нас профит от 3 до 10 раз. А на конкретных участках, где мы прицельно выжимали максимум, — до 20–50 раз.

    Это не значит, что можно просто взять PHP-код и он будет работать в 10 раз быстрее. Нет: рандомный сниппет, даже будучи скомпилированным, может и не впечатлить, потому что чаще всего там навыводится mixed.

    Это значит, что PHP-код можно превратить в быстрый, если думать о типах и использовать built-in KPHP-функции.


    KPHP и IDE


    Система типов в KPHP значительно шире и строже, чем в PHP. Мы уже говорили, что нельзя смешивать в массиве числа и объекты — потому что какой тогда тип элементов этого массива?

    function getTotalAndFirst() {
      // пусть $total_count это int, $user это объект User
      ...
      return [$total_count, $user]; // нельзя
    }
    

    Нельзя! А как можно? Например, сделать отдельный класс с двумя полями и вернуть его. Или вернуть кортеж (tuple) — специальный KPHP-тип.

    function getTotalAndFirst() {
      ...
      return tuple($total_count, $user); // ok
    }
    

    К функции можно даже PHPDoc написать, KPHP его прочитает и после стрелочки (->) поймёт:

    /** @return tuple(int, User) */
    function getTotalAndFirst() { ... }
        
    [$n, $u] = getTotalAndFirst();
    $u->id;  // ok
    

    Но вот проблема: KPHP-то понимает, а вот IDE нет. Ведь tuple — это наша придумка, как и разные другие штуки внутри PHPDoc.

    Не так давно у нас появился KPHPStorm — плагин для PhpStorm, который расширяет подсказки, оставляя рабочим рефакторинг. А ещё сам трекает сходимость типов значительно строже нативного.

    Если вы интересуетесь разработкой плагинов для IDEA — загляните, все исходники открыты. KPHPStorm глубоко внедряется во внутренности IDE (через кучу недокументированного API). Многое пришлось пройти, чтобы всё заработало. Спасибо ребятам из JetBrains за помощь.


    Закругляемся: вот он Open Source, что дальше?


    Мы усовершенствовали KPHP и показываем его вам: можно посмотреть, покомпилировать что-то простое — теперь есть все инструкции и даже Docker-образ. Но будем честны: KPHP пока остаётся инструментом, заточенным под задачи VK, и для более широкого применения в реальных сторонних проектах он ещё не адаптирован.

    Почему так? Мы всегда поддерживали в первую очередь собственные движки ВКонтакте. KPHP не умеет в Redis, MongoDB и другое. Даже Memcache у нас свой, который по RPC работает. Даже перед ClickHouse, который у нас развёрнут, стоит собственная proxy, куда мы тоже ходим по TL/RPC.

    Мы никогда не поддерживали стандартные базы, потому что это не было нужно. Но знаете, в чём прикол? Если мы не выйдем в Open Source, этого никогда и не произойдёт — потому что это так и не потребуется. За последние два года KPHP прошёл огромный путь, возродился. Мы можем ещё пару лет продержать его у себя. Можем покрыть возможности PHP 8, сделать ещё ряд оптимизаций, освоить микросервисы и интеграцию с Kubernetes — но нам не будут нужны стандартные базы. И через два года будет то же самое.

    Только открытость и внешняя заинтересованность помогут выделить дополнительные ресурсы, чтобы пилить фичи не только для нас, но и наружу. Может, уже среди читателей этой статьи найдутся те, кому интересно с нами развивать это направление? Почему нет — у нас очень маленькая команда, и мы занимаемся интересными, глубокими и совершенно не продуктовыми вещами.

    Теперь вся разработка KPHP будет вестись на GitHub. Правда, CI пока останется в приватной инфраструктуре. Движки по-прежнему будут закрыты — но когда-нибудь команда движков, надеемся, тоже решится вынести в сообщество хотя бы часть кода.

    У вас может возникнуть вопрос: а сложно ли добавить поддержку протоколов MySQL, Redis и других? И да и нет. Если пробовать интегрировать готовые модули — скорее всего, будет фейл. Особенно если они порождают дополнительные потоки, ведь воркеры принципиально однопоточные. К тому же, просто поддержать протокол, может, и не проблема — но сложно сделать его «прерываемым», чтобы это стыковалось с корутинами. А вот к этому сейчас код совершенно не готов: там корутины тесно переплетены с сетью и TL. Непростая история, в общем :) Но выполнимая, и над этим надо работать.


    Итак: где ссылки, как попробовать


    GitHub
    Документация
    FAQ

    Мы рассчитываем, что в дальнейшем нашей команде — возможно, при помощи сообщества — удастся развить KPHP так, чтобы он стал полезным инструментом и вне ВКонтакте. Не так важно, как быстро это произойдёт. В любом случае, это тот ориентир, который теперь стоит перед проектом.
    ВКонтакте
    Один из самых высоконагруженных проектов рунета

    Похожие публикации

    Комментарии 53

      +1
      Рад слышать, что проект развивается.

      Вопрос по этапу оптимизации. «безопасно заинлайнить простые функции» — это про php код? А почему не отдать такие оптимизации всецело в руки с++ оптимизатора? Или есть какие-то граничные случаи, когда он не видит, что можно заинлайнить?

      И ещё вопрос по организации работы между командами. Вот я написал какой-то php код, компилирую kphp и у меня ошибка где-то на уровне g++. Что вообще происходит в этом случае? Сразу запрос в вашу команду (aka ошибок с++ не должно быть) или разработчик сам разбирается?
        +2
        Про заинлайнить — на самом деле g++ и инлайнит :) А вот прикинуть, какие функции при кодогенераци разбивать на cpp/h, а какие помещать в h с пометкой inline или __attribute__((always_inline)) — это уже KPHP решает. С LTO на десятках тысяч функций там всё не совсем гладко.

        Про ошибки на уровне g++ — по-хорошему, конечно, их не должно быть, они должны отбиваться на уровне трансляции с разумной ошибкой. Но в каких-нибудь редких неучтённых сценариях проскакивают — и тогда, обычно, да, PHP-разработчики приходят в наш чат :)
        +1

        FAQ.


        Q: Что такое "движки"?
        A: s/движки/сервисы/ (или "демоны") — сервис сообщений, сервис лайков, etc.


        Из статьи:


        Например, нельзя обращаться к полям по имени или вызывать так методы.

        Q: А как происходит обращение к полям из KPHP?
        A: По именам, но без динамических выражений.


        $this->field // ok
        $this->$fieldname // not ok
          +10
          Это всё занятно, но остальной мир переписывает проекты на Go, Java, .NET, чтобы была типизация и скорость. А вы изобрели велосипед, причём в одном колесе нет половины спиц, а второе квадратное.
            0
            Я наверное что-то не понимаю, но куда уж быстрее чем C/С++?
            +7

            Я задавал похожий вопрос товарищам из фейсбука когда они мне расказывали про HipHop, и задам его еще раз — кроме исторических причин (куча кода на PHP), есть ли преимущества перед реализацией продукта сразу на типизированном языке, с JIT или компиляцией в машинный код?


            Аргументом, разумеется, является то, что "а вы знаете сколько у нас строк на PHP?!?". На мой взгляд, аргумент это довольно слабый: в конце концов, никто не мешает разбивать код на микросервисы, связанные RPC, и переписывать их по отдельности, начиная с самых критичных по производительсности (вариант: с самым загнившим кодом). В конце концов, те части, которые действительно проще написать на PHP, можно на нем и оставить — выполняя все performance-critical задачи в native code.

              +4
              PHP8 — это уже и есть типизированный язык с JIT :)
                +1

                Ну как вариант перевести все на PHP 8 — опять же, используя микросервисную архитектуру чтобы не релизить весь миллион строк кода за один раз.


                Кстати про то что PHP 8 типизированый и JIT не знал — очень давно не писал на PHP, уже не в курсе что происходит в этом мире :)

                  0
                  PHP8 — это уже и есть типизированный язык с JIT :)

                  Ассерты в рантайме(а именно ими тайпхинты и являются) — не типизация. Ну или не статическая типизация, если вам такая терминология по душе, хотя в контексте kphp очевидно что речь не о «динамической».
                  Ну и по скорости PHP 8 по прежнему на порядок отстаёт(и будет отставать) от мейнстримных и не очень компилируемых языков.
                  +6
                  Если честно, нет других причин кроме исторических. Как бы слабо ни звучал аргумент «у нас миллионы строк кода» и как бы пафосно сверху ни спускали мысль «давайте всё попилим на микросервисы» — реальность не такая. В реальности всё достижимо из всего, а цикломатичность кода стремится к бесконечности :)

                  Да, в первую очередь нужно приводить в порядок код. Независимо от того, бить на микросервисы или нет. Но по опыту скажу: те части, где нет Legacy и которые в порядке, они и на PHP очень даже стройно смотрятся. А с учётом того, что и по перформансу теперь быстро — то нам более чем подходит такой вариант.
                    –1

                    А посему не пошли в сторону php8?

                      +3
                      Потому что он появляется вот только-только сейчас, а проблемы с производительностью мы решаем уже несколько лет? )

                      Я более чем уверен, что для наших задач мы всё равно будем значительно быстрее, уж слишком много сил для этого приложили и слишком много неочевидных моментов под себя заточили, которые не опишешь в статье.
                      Хотя не исключаю, что PHP 8 в синтетических случаях случайно может оказаться сравним.
                        +1
                        Почему разработчики предпочитают создавать свой велосипед, вместо того, чтоб контрибьютить в PHP?
                        Может если приложили бы усилия для повышения производительности в PHP, jit появился бы на пару лет раньше и увеличил производительней языка. За следующие пару лет дооптимизировать jit до максимума производительности, и отпадет обходимость в использовании kphp и hiphop.
                          +2

                          Я думаю, причина примерно та же, почему ФБ сначала реализовал транслятор в C++ — есть такое распространенное (и нельзя сказать, что необоснованное...) мнение, что никакой JIT не будет быстрее хорошего кода на плюсах. В случае с тем, как реализованы классы в KPHP, я лично сомневаюсь, что JIT в PHP в скором времени сможет догнать простой код на плюсах, который генерит KPHP, а усилий приложить придется намного больше.

                            +1

                            Если почитать отчёты Фейсбука, почему они закрыли HPHPc в пользу HHVM, то там, в том числе, и проблемы с производительностью. JIT догнал и перегнал у них компиляцию, а как наращивать производительность у компиляции они придумать не смогли.


                            Другая проблема — это гигантские файлы, которые надо уметь ещё и быстро деплоить по всей инфраструктуре.


                            Другие причины, вроде плохой поддержки php- фишек компиляцией, можно опустить, так как они в итоге и для JIT отказались от нативного PHP в пользу Hack.

                            +3

                            Одна из причин — необходимость двигаться намного быстрее. С PHP все-таки придется иметь дело с процессом RFC и оглядываться на потребности всего сообщества, а не только конкретной компании с конкретными задачами.

                              0
                              youROCK Почему JIT не сможет приблизиться по скорости коду на плюсах? Там и там компилируемый код, на плюсах заранее скомпилится, с jit на лету, на следующих запросах будет использоваться скомпилированные файлы из кэша.

                              Интересно сейчас посмотреть сравнение kphp с php8, а не 7.4

                              pronskiy Так автор поста пишет что потребовалось около двух лет, чтоб вдохнуть новую жизнь в kphp и плюс еще сколько лет сделать первую версию kphp и это только процедурный код, без ООП (или я что-то пропустил). Не похоже, что этот подход быстрей RFC в PHP.
                                –2
                                Интересно сейчас посмотреть сравнение kphp с php8, а не 7.4

                                Вряд ли это будет показательно. JIT в PHP в самом начале пути. На конфах разработчики прямо об этом говорят, что никаких особых оптимизаций там сейчас нет. По сути, выведение типов, чтобы проверки убрать (играет роль только для циклов), и ещё какие-то общие мелочи.
                                  –1
                                  Почему JIT не сможет приблизиться по скорости коду на плюсах?

                                  Может. На синтетике может и перегнать:


                                  Заголовок спойлера

                                  Сырцы: https://gist.github.com/dstogov/12323ad13d3240aee8f1
                                  Результат: https://www.youtube.com/watch?v=clMhVFllMTM&feature=youtu.be


                                  P.S.
                                  1) Вывод в stdout у пыха очень медленный, что явно видно на видео. У сишных сырцов только тыкаешь — оно сразу показывается. А в PHP есть задержка, которая явно заметна на видео. Результат не сразу появляется на экране. Как это затюнить — не представляю.
                                  2) В сборке отключены все расширения (кроме опкеша) и затюнен буфер jit, перед запуском пыха явно всё прогрето.


                                  Ну т.е. тесты совершенно не объективные и снимались ещё в сентябре, просто в каком-то холиваре в чатике, просто что б показать, что если затюнить, то каким-то невероятным боком можно сишки обогнать.

                          +1
                          Скорее всего PHP-разработчики не очень умеют писать на чём-то ещё, а менять весь штат разработки глупо и опасно. Но может со временем придут к этому.

                          На фронтенде у ВК при Дурове был голый JS с множеством самописных костылей, а сейчас вот новые модули стали и на реакте делать.
                          0
                          В PHP универсальный контейнер внедрения зависимостей легко и удобно реализуется через рефлексию. Но если вы говорите, что в KPHP рефлексии нет, то хотелось бы посмотреть пример реализации этого контейнера в рамках возможностей языка.
                            +1
                            Интересно бы посмотреть на производительность в сравнении со Swoole / Workerman.

                            Запилите тесты для Techempower Benchmark?
                              0
                              Кстати, а сколько нужно оперативы для линковки этого ./vk? Машин разработчиков вообще хватает, или только в CI?
                                +4
                                У нас всё-таки чересчур много кода, и нестрипанный бинарь получается почти 3 ГБ, который линкуется из более чем 100к объектников (не втупую, там свои приколы). Поэтому линковать его на локальной машине это так себе затея) Но унести бинарник vkcom на флешке домой это можно, получается.
                                +4

                                Спасибо за статью и за то, что открыли исходники!


                                Однако, как у человека, который работал в ВК не так давно, хотел бы уточнить один момент:


                                Если говорить про бенчмарки, то на средних VK-страничках у нас профит от 3 до 10 раз. А на конкретных участках, где мы прицельно выжимали максимум, — до 20–50 раз.

                                Расскажите пожалуйста, как Вы тестировали, и на каких страницах есть выигрыш, и почему. Когда я пару лет назад тестировал производительность сайта под PHP 7.2, для более-менее честного сравнения отключался xdebug и development режим сайта, поскольку оба дают очень существенный оверхед при исполнении. В таком режиме многие страницы, где не используются асинхронные запросы (в PHP коде вместо этого ведь просто заглушки стоят), выполнялись со сравнимой скоростью (страницы вроде списка друзей или мессенджера отрабатывали за такое же время, как и KPHP, плюс-минус 10%). Некоторые страницы, вроде ленты, действительно сильно заточены под KPHP и работали в ~10 раз медленней, но в общем и целом разница была не такая большая, и точно не в 3-10 раз. Причём во большинстве случаев разница была из-за того, что в PHP не реализован тот вид асинхронных запросов, который есть в KPHP, но само ядро PHP вполне поддерживает те же концепции, поскольку как минимум есть конструкция yield, которая точно также сохраняет состояние функции в произвольном месте и умеет восстанавливать его обратно.


                                При этом, конечно же, на коде, который использует полностью типизированные классы и структуры вряд ли что-то может быть сильно быстрее KPHP, потому что он буквально транслирует этот код в C++ почти что один-в-один, и, как мне кажется, это и является единственным существенным преимуществом KPHP перед современными версиями PHP, и, вероятно, даже с появлением JIT в PHP8 всё равно производительность KPHP будет выше, если специально затачивать код под него.


                                Просто хотелось поменьше голословных утверждений про производительность KPHP на реальной кодовой базе и больше деталей и примеров по поводу того, какой код действительно ускоряется с помощью KPHP, а какой — нет, или даже работает медленней современного PHP, который тоже на месте всё это время не стоял.

                                  +1

                                  Юрий, по-моему, ты сам ответил на свой вопрос :) Ты ушёл из ВК как раз чуть меньше 2-х лет назад, когда наша разработка входила в активную стадию. На тот момент у нас были просто структуры (без ООП), и мы только начинали свой путь. Но кстати даже тогда, когда повсюду были ассоциативные нетипизированные массивы — даже тогда KPHP выигрывал больше, чем 10%, тут ты занижаешь. Хотя не в 3-10 раз, как сейчас, тут конечно.


                                  И с тех пор как раз мы взяли курс на тотальную типизацию. Полная поддержка ООП, напрямую идущая в плюсы. Типизировали RPC-слой с движками, что TL-байты напрямую маппятся в PHP-типы в обе стороны. Обязали писать типы везде: даже если у тебя legacy и везде mixed — иди и пиши явно. Плюс ряд оптимизаций, не связанных с типизацией — чего стоит одна шаренная память между воркерами с иммутабельным доступом. По факту, почти всё что мы сделали — это как раз после твоего ухода )) Так что да, теперь KPHP значительно быстрее — и как ты правильно сказал, в основном за счёт типизации и за счёт фич, которых в PHP нет. А если его использовать абы как, то PHP кое-где случайно и может окажется быстрее. Но это не наш случай.

                                    +1
                                    Но кстати даже тогда, когда повсюду были ассоциативные нетипизированные массивы — даже тогда KPHP выигрывал больше, чем 10%, тут ты занижаешь.

                                    Ну времена работы многих страниц (повторюсь, в основном, конечно же, тех, где нет или почти нет асинхронного кода) правда мало отличались. Так и должно быть, потому что основной выигрыш в производительности от PHP7 по сравнению с PHP5 как раз и был в том, что были оптимизированы внутренние хэш-таблицы, а JIT, как оказалось, на реальном PHP-коде давал не так уж и много.


                                    Хотелось бы всё-таки увидеть полноценную методологию тестирования с цифрами (хотя бы относительными, чтобы не нарушать NDA и прочее) и анализом CPU usage и прочего, чтобы сравнивать именно производительность PHP без учета асинхронного кода против производительности KPHP без учета асинхронного кода. Также нужно сделать сравнение TL-схемы честным, чтобы и для PHP и для KPHP использовались одинаковые механизмы (поскольку для PHP тоже можно десериализовать TL-схему в расширении и генерировать готовые классы сразу из расширения, и это будет на порядок быстрее десериализации на самом PHP), отключить xdebug и использовать production-режим, где не считаются стректрейсы для отладки на каждый вызов RPC. И также не резолвятся имена хостов без нужного кеширования (т.е. вместо localhost используется 127.0.0.1, например).


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


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

                                    Расскажите про это подробнее как-нибудь, пожалуйста :).


                                    А если его использовать абы как, то PHP кое-где случайно и может окажется быстрее.

                                    Совершенно не случайно. Ядро обычного PHP тоже оптимизировано очень хорошо для того кода, который обычно встречается на практике (то есть этот код не содержит строгих аннотаций типов, например). Плюс в opcache в PHP точно также кешируются константные значения, даже для массивов, и в целом оверхед от рантайма обычного PHP меньше, например в плане потребления памяти, чем в KPHP. Ну и в целом обычный PHP тестируется на реальных кодовых базах за пределами VK, так что несложно представить себе ситуацию, что в том же opcache или в SSA представлении будут существовать оптимизации, которых нет в KPHP, но для конструкций, которые часто используются в сторонних (по отношению к ВК) кодовых базах на PHP.


                                    Но это не наш случай.

                                    Я бы не был так категоричен. Код ВК тоже является в первую очередь кодом на PHP, и во вторую это уже некоторое подмножество, которое хорошо работает с KPHP. Тем не менее, я рад, что вы выложили свои наработки в open source и надеюсь, что поддержка ООП и прочего в KPHP уже достаточно хороша для того, чтобы можно было запустить какие-нибудь реальные проекты на его основе без слишком больших модификаций. Надеюсь увидеть от вас больше статей, но в которых поменьше хвастовства и побольше «мяса» и технических деталей.

                                  +6
                                  Мы рассчитываем, что в дальнейшем нашей команде — возможно, при помощи сообщества — удастся развить KPHP так, чтобы он стал полезным инструментом и вне ВКонтакте. Не так важно, как быстро это произойдёт. В любом случае, это тот ориентир, который теперь стоит перед проектом.

                                  Честно говоря, не очень понятно, зачем эта цель (и вам, и сообществу). В конкретном случае (очень много легаси-кода на PHP) — да, ок, вероятно это имеющее право на жизнь решение. Но мне сложно представить ситуацию, в которой у кого-то ещё снова может возникнуть подобная необходимость. Это нужно ведь, чтобы кто-то решил начать новый проект на PHP (а не на Go, скажем, да и даже не на PHP 8 почему-то), вырос до тех размеров, когда уже нет желания переписать на какой-то другой язык — но при этом есть желание переписать код так, чтобы он успешно компилировался kPHP…

                                  Кажется, вы зря возлагаете надежды на сообщество. Пусть вы и оформили kPHP покрасивше, чем в 2014, но пользы для кого-то, кроме самого ВК, от него немного. А если уж кто-то и горит желанием развивать нечто компилирующееся в C++, я бы предложил лучше потратить силы на Nim, например, — у него, по крайней мере, нет цели одновременно подражать другому языку (который сам по себе так себе образец для подражания), и при этом отбирать из него целые пласты функциональности :)
                                    +3

                                    Побуду немного адвокатом дьявола. В VK разработка идет на обычном PHP, а KPHP используется только для финальной сборки кода на продакшене, поэтому разработческий опыт при разработке с использованием KPHP и при использовании обычного PHP отличается слабо. При этом, PHP до сих пор очень хорошо подходит для разработки в вебе, в том числе и для новых проектов, даже по сравнению с языками с быстрой компиляцией, такими как Go. В PHP действительно работает парадигма «сохранил файл, перезагрузил страницу, посмотрел, что изменилось», и это очень сильно помогает при разработке, особенно в начале.


                                    Даже если у вас есть большой готовый проект, который мало использует рефлексию и прочее (хотя бы из соображений производительности) и код всё равно из себя в основном представляет из себя ООП с уже прописанными аннотациями типов, KPHP может дать очень большой выигрыш в производительности за счёт того, что он компилируется в почти что эквивалентный C++. Стоит ли того переделка кодовой базы (или допиливание фич в KPHP) или нет, решается на основе того, сколько стоят альтернативы: переписать существующую и хорошо отлаженную, пусть и legacy, кодовую базу с нуля на Go будет намного дороже и рискованней для бизнеса, чем адаптация существующего кода под новые реалии. Если производительность PHP-кода действительно становится узким местом и у вас много серверов, то, как мне кажется, вариант с KPHP может и взлететь, даже если для этого нужно будет добавить в KPHP какие-то недостающие для вас фичи.

                                    0
                                    PHP: у нас есть классы, интерфейсы и наследование; есть лямбды, трейты и Composer;
                                    А как используется composer, если нет рефлексии, магических методов, и кучи всего? Перед использованием библиотеки пересматриваете её исходники? А вдруг её обновят? Или композер используется исключительно для внутренних же библиотек, пакетов, да и просто как автолоадер?
                                      0

                                      Да, всё так. Для распространения своего же кода, для распила монолита, это удобно и стыкуется с привычной разработкой.

                                      –1
                                      Был такой проект Phalcon. И этому добру туда же

                                      UPD. Удивлён, но Phalcon жив и релизится
                                        0

                                        А почему "был"? Я им никогда не пользовался, но вроде как проект жив, и даже новую версию в сентябре анонсировали.

                                          0
                                          Да, вы правы, вижу он действительно релизится. Хотя почему-то был уверен, что в этом году видел новость про закрытие проекта
                                            0
                                            Новость была не про закрытие проекта, а про отказ от развития Phalcon как скомпилированного подключаемого модуля. Предполагалось, что новая версия Phalcon будет написана на PHP и иметь производительность на уровне других PHP-фреймворков.
                                            habr.com/ru/post/518064
                                              0
                                              Точно. Но по сути это и есть отказ от своего «кпхп»
                                                +1

                                                Да, не знал. Тут я с вами соглашусь. Интересно, как долго Фалькон просуществует в новой ипостаси.

                                            0
                                            Он жив конечно же, но уже со времен php 7.4 его преимущество по скорости стало не таким явным. Кроме развития самого php, другие фреймворки тоже не стояли на месте все это время. Появилось много легковесных фреймворков, а такие монстры как Symfony заметно «похудели» и ускорились за прошедшие годы. И вот сейчас, в конце 2020-го года я не смогу придумать ни одной веской причины начинать разработку нового проекта на именно фальконе, хотя когда-то сделал на нем достаточное количество проектов и активно «топил» за переход на него с монструозных Zend или Symfony.
                                          +1
                                          В 2014 году в техническую команду пришли новые люди, которые начали заниматься движками. (Да, ВКонтакте до сих пор десятки собственных закрытых движков, хотя точечно мы используем ClickHouse и другие известные.) Но KPHP долго никто не трогал. До 2018-го бэкенд-код действительно был на уровне PHP 4.5, а IDE плохо понимала код и почти не подсказывала при рефакторинге.

                                          Интересно было бы узнать, каковы шансы, что KPHP опять не уйдет в тьму на 6 лет? Есть какая-то дорожная карта?


                                          И второй вопрос — насколько сложно лучшие достижения KPHP перенести в основной PHP? И есть ли в этом вообще смысл?

                                            0
                                            > каковы шансы, что KPHP опять не уйдет в тьму на 6 лет
                                            По крайней мере, если в 2014 выложили и всё, то сейчас у нас весь процесс разработки переехал на гитхаб. Да, не факт, что у нас будет много времени на то, чтобы делать что-то для не нужд ВК (и в ближайшее время точно не будет) — но весь процесс разработки в любом случае становится открытым. Так что ответ зависит от трактовки слова «тьма» :)

                                            > лучшие достижения KPHP перенести в основной PHP
                                            Думаю, что шансов примерно 0. Во-первых, мы совершенно сторонняя компания, не имеющая никакого отношения к Zend. Ну и во-вторых, мы ведь живём за счёт ограничений и за счёт анализа всех исходников целиком, а не по мере исполнения.
                                              +2
                                              Во-первых, мы совершенно сторонняя компания, не имеющая никакого отношения к Zend.

                                              Как и JetBrains, Badoo, Facebook и другие компании, которые очень много сделали для языка.


                                              Ну и во-вторых, мы ведь живём за счёт ограничений и за счёт анализа всех исходников целиком, а не по мере исполнения.

                                              PHP 7.4 прелоадинг живёт тоже за счёт анализа исходников перед их исполнением. Понимаю, что это далеко не аналог шага компиляции, но сам механизм "сборки перед исполнением" есть.

                                                0
                                                PHP 7.4 прелоадинг живёт тоже за счёт анализа исходников перед их исполнением.

                                                Прелоадинг решает вполне конкретную проблему пустого opcache при старте. При этом имеет существенные недостатки, ограничивающие его применение.
                                            –1
                                            Но вот проблема: KPHP-то понимает, а вот IDE нет.

                                            И IDE тоже понимает


                                            /** @return array{0: int, 1: User} */
                                            function getTotalAndFirst(): array {}
                                              0

                                              Я думаю, вы про https://plugins.jetbrains.com/plugin/9927-deep-assoc-completion ?


                                              Из коробки PhpStorm, кажется, такое не поддерживает.


                                              А кроме автодоплений для tuple и shape типов плагин умеет ещё и другие вещи.

                                                0

                                                Нет, про psalm, который нынче уже из коробки идёт.

                                              +2
                                              Почему бы не использовать LLVM? PHP -> LLVM IR -> .o + gold -> ваш бинарник.
                                                0
                                                транслировать в высокоуровневый язык сильно проще, чем в LLVM
                                                  0

                                                  LLVM это интересный поинт, возможно когда-нибудь и протестируем, но, конечно, чтобы покрыть всё что нам надо — это по факту переписать не только всю кодогенерацию, но и множество вещей вокруг. А этим есть смысл заниматься только при условии, что гарантированно почему-то станет лучше.
                                                  А так, когда-нибудь на синтетике сами попробовать хотели, да.

                                                    +2
                                                    Будет лучше хотя бы в том, что вы сможете уйти от C++ вообще и g++ в частности. Получите более гибкую настройку оптимизатора + легко сможете добавить свои оптимизации. Или например, зачем вам плюсовый ABI, легко сможете от него отступать или сделать вообще свой.
                                                  +3
                                                  Я подразумеваю, что первый раз бэкенд VK (а ведь это не только kphp, а ещё и бд, и движки для многих типов сущностей) был выложен для того, чтобы на нём крутить бэкенд Telegram юридически легальным способом.

                                                  Зачем же выкладывать обновления повторно?
                                                    +1
                                                    Пусть в качестве ответа на «Зачем?» будет «Но ведь хуже-то от этого не станет».
                                                    Открытость для нас как для бренда компании — это весьма достойно, хоть и многими воспринимается с изрядной долей скепсиса. Открытость для нас как для разработчиков — это как минимум личная ценность, что ты не просто «где-то там работал», а результаты твоих трудов доступны и лежат в открытом доступе. Тем более, что компиляторная область в целом весьма узкая.
                                                    И раз мы переезжаем на гитхаб — то конечно, куда без статьи на Хабре :) Ничего сверхестественного.
                                                      –1
                                                      И это здорово!
                                                      Я как раз искал язык программирования для веба под лицензией GPLv3
                                                    0
                                                    Интересно было бы посмотреть тесты в каком-то виде.
                                                    Что-то вроде такого benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/php.html

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

                                                    Самое читаемое