Pull to refresh
66
-5
Александр Кирсанов @unserialize

Руководитель команды KPHP

Send message

Привет! Должны поддерживаться 2022.2 и выше. Для установки нужно внести дополнительный url, т.к. плагины из РФ не добавляют в Marketplace (эти действия описаны на страничке установки).

А у тебя какая версия? Можешь в чат добавиться, там будет оперативнее, наверное :)

Всё правильно. Не поспорить. Это эффект любых enterprise'ов и корпораций.

Пока команда маленькая, знания хорошо пошарены и каждый представляет примерно весь проект — есть возможность всё контролировать. Как только начинают выделяться отдельные команды, юниты, иерархия, области ответственности, противоречивые KPI — начинается нагромождение, абстракции, перекладывания данных. И микросервисы в кубере конечно же давайте затащим!!! Но это уже личное :D

Это везде так. Я не знаю, что с этим делать. В моих силах лишь максимально долго сохранять порядок на низком уровне. И пока это получается хорошо.

"Планируете ли отрезать пуповину?" — этот вопрос выглядит скромным, а вот "и когда?" уже и правда нет :D

Ох, сколько же раз я над этим думал — и до сих пор не нахожу правильного ответа.

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

Но правильно ли это? Интерпретируемая разработка на PHP — это очень удобно для бэкендеров. Максимально быстрое прототипирование: залил файл на сервер, F5, и готово. Плюс, используем привычные инструменты: пройтись по шагам в xdebug, прогнать тесты на PHPUnit, всякие там моки и рефлексия, всё это во время разработки — а KPHP это не поддерживает и не должен. Он для продакшена.

Если отпочковываться — это как? Уходить от интерпретируемой разработки, заставляя бэкендеров после каждого изменения компилировать гигантский бинарь? Слишком долго, все взвоют. Сделать свой модный форк PHP, тоже интерпретируемый? Но сразу же разойдёмся и законфликнуем с основной версией. Да и любой язык — это далеко не синтаксис, это прежде всего экосистема вокруг. А дебаггер? А тесты? А написание кода в IDE, причём в разных? А подсветка в гитхабе/гитлабе? Уйма примеров. И всё это нужно делать? И поддерживать? Здесь bus factor не то что единичка, по-моему он отрицательный будет )

(если интересно, в [другой социальной сети] Hack'ом занимается отдел более чем из 50-ти человек, уже 10 лет)

В общем, вот такие примерно соображения. Так что — я не знаю, в какую сторону качнуть этот trade-off :)

Так в этом и смысл — чтобы не запускать llvm и т.п. для препроцессора. Ведь это нужно делать локально, для каждого cpp-файла, при вычислении его зависимостей. Препроцессор это дорого. Ну то есть это нормально, но когда мы хотим выжать максимум — то дорого.

Кастомная парсилка появилась не просто так, а уже в самом конце — и тоже разгрузила CPU на локальной машине. Изначально всё по-простому действительно через препроцессор гонялось.

Если уж честно, то я сам не понимаю, почему НАСТОЛЬКО много PHP-кода. Иногда поглядываю статистику и ужасаюсь: капец, за полгода ещё миллион строчек добавили, ну как??? Бедный, думаю, KPHP, как он вообще справляется. Но уж что имеем, то имеем. Проблема всех больших компаний, а также монореп с бесконечной цикломатичностью кода ( И уж явно не компилятора.

Запускается, на самом деле, достаточно быстро. Там скорее сложность другая, ведь нельзя погасить старый бинарник и запустить новый вместо него — ведь старый в режиме нон-стоп обрабатывает запросы. Вместо этого используется graceful restart: поднимается рядом новый бинарник и начинает постепенно "утягивать" новые коннекты со старого, а старый продолжает работать, пока не завершит последний скрипт, и потом умирает. Соответственно, за это время, пока коннекты плавно перетекают, сам собой происходит прогрев локальных кешей и другая предварительная инициализация.

А вот раскидывать бинарник такого размера на много тысяч бекендов, даже через gossip deploy — вот это и правда долго, несколько минут :(

Ну, я чуть упростил картину, конечно. Разрабатывают действительно на обычном PHP. А вот когда нужно: если хотят полазить по быстрой версии сайта / если хотят убедиться, что на KPHP тоже работает так же (особенно что касается корутин, параллелизации и других штук, которые реализованных в PHP через полифиллы) / выложить ветку для тестировщиков — тогда и нужна полная сборка. Для тестировщиков, например, разные версии сайта от разных разработчиков деплоятся больше сотни раз за день.

Конечно, для .cpp нужно скопировать все include по всему дереву. nocc ровно это и делает (1 .cpp + 49 .h загружает, например). Впоследствии, даже если сам .cpp изменится — большинство include'ов из дерева зависимостей окажутся уже загруженными (1 .cpp + 2 .h, условно, остальные 47 уже там).

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

А вот здесь всё не так :)

Мы пользуемся тем, что мы написаны на PHP. Поэтому VK разрабатывается и прекрасно работает на обычном PHP тоже (только медленно).

Так что бекендер правит строчку в IDE — автоматически через SFTP зеркалится на сервер — и всё. Никакой компиляции. Только интерпретация, будто обычный PHP-сайтик.

Так что разрабатывается на PHP. Юнит-тесты тоже (PHPUnit это всякие там моки, рефлексия — в KPHP этого нет и не должно быть). А KPHP-сборка — это только для продакшена. Базовый флоу именно такой.

Ну и в гит хуках, когда создаёшь MR'ы всякие, там KPHP тоже прогоняется — но там просто трансляция PHP->C++, а это быстро (сборка и линковка не нужны). Предполагаем, что если KPHP на фазе трансляции не упал, то код валиден и конечный C++ на проде уже соберётся и будет работать ровно как изначальный PHP.

Поэтому — совсем не больно разрабатывать такой бекенд :)

Не секрет :)

С дебаг-символами около 6.5 ГБ.

Без дебаг-символов (стрипнутый) — 1.3 ГБ.

На продакшене крутится стрипнутый бинарь (именно он раскидывается на тысячи бекендов и обслуживает http-запросы). А бинарь с дебаг-символами лежит отдельно и нужен, чтобы запускать addr2line на нём в случае проблем (по адресам, полученным с продового бинарника).

Предполагается, что на всех серверах компилятор одинаковый — ровно тот же самый, что и локально. То есть не важно, где исполнить `cxx 1.cpp` — локально или на сервере Х. Даже если везде g++, но разных версий — я считаю это undefined behaviour. Можно добавить какие-то проверки, но мне кажется, это должно решаться именно на уровне конфигурирования.

Поэтому, если nocc-server поставить на ARM'овые сервера, то всё заработает (проверяли).

Кросс-компиляцию тоже можно поддержать, добавив анализ -sysroot / -isysroot, поиск хедеров в нужных папках на клиенте и подмену этих опций на сервере. Я просто их не добавлял на данный момент, но это не сложно.

Я могу быть неправ, но по-моему здесь немного другое. Bazel, вероятно, хорош для статичных проектов, но не для автогена.

Bazel это всё-таки система сборки, ВМЕСТО Cmake/make. В нашем случае, когда 200к файлов это автоген, то чтобы заюзать Bazel, помимо собственно cpp-шных исходников, пришлось бы генерить Bazel-проект (конфиг?), содержащий ссылки на эти все файлы или glob-выборки. И потом запускать Bazel для сборки бинарника.

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

В случае KPHP, поскольку он сам и так знает, что там за дифф (он же его генерит и записывает), он просто запускает CXX (g++ или nocc g++) для нужных cpp.

Долго :) Дольше, чем в итоге компиляция с нуля.

Мы используем partial linking: условно, эти 200к файлов разбиты на 100 папочек, по 2к каждов в каждой. Каждая папочка линкуется отдельно (получается 100 объектников) — это можно делать параллельно, делается весьма быстро. А потом 100 объектников линкуются в один большой. И вот это уже долго.

Мы используем lld. Итоговый бинарь линкуется почти 2 минуты, и не очень понятно, что с этим сделать. Когда был просто ld — было почти в 3 раза дольше, всё-таки lld значительно шустрее.

Пробовали завести новый модный mold linker (https://github.com/rui314/mold) — не срослось, он выдаёт закоррапченные бинарники, которые не запускаются, причём нестабильно, как повезёт.

Как я понимаю, дерево исходников синхронизируется между нодами средствами самого nocc?

Между серверными нодами ничего не нужно синхронизировать: для компиляции a.cpp на сервере S1 достаточно, чтобы все зависимости рекурсивно находились на сервере S1. Они не нужны на S2, S3 и т.д. Ведь a.cpp попадает всегда на S1 (по имени файла).

ваш инструмент постройки дерева зависимостей не умеет препроцессор?

Чтобы определить все зависимости #include рекурсивно (а это делается на клиенте, т.е. для каждого из 200к файлов), используется кастомная парсилка инклудов. `#include "some-file.h"` она зарезолвит, а вот `#include GENERATE_FILENAME(...)` уже нет: здесь без запуска препроцессора на стороне клиента обойтись никак. В нашем случае — и в большинстве случаев в целом — пути к инклудам статичные, без макросов. Кастомную парсилку можно отключить, что приведёт к вызову препроцессора локально на каждый cpp, но это конечно сразу будет дольше.

О, не слышал про такой инструмент. Выглядит как что-то похожее, и правда.

Есть сомнения, что он сможет выиграть по скорости в нашем случае, всё-таки здесь мы очень постарались. Всякие там серверные pch-файлы и собственный типа препроцессор срезают очень много локального времени помимо remote-кешей. По доке пока непонятно, как он работает, совсем свежая тулза.

Но сравнить интересно, да. Звучит как задание для какого-нибудь стажёра в будущем :))

Хорошие пункты.

По поводу классов и неймспейсов — я даже их немного упоминал в статье. Цвета кажутся более общим что ли свойством, точнее даже не свойством — это другой "вектор" кода, не зависящий от имплементации, на один класс разбита функциональность или на 10. А неймспейсы могут быть частным случаем цветов.

Про пакеты тоже всё верно, они больше про модульность и изоляцию. Хотя не все: в тех же PHP пакетах, хоть они и вынесены в Composer, никто не мешает обратиться к каким-то внутренностям этого пакета. А в нашем случае это прежде всего придумывалось для монолита, где большая связанность и изолировать пока что ничего не можем.

Про "граф зависимостей можно посмотреть и глазами". Тоже так думал, пока не начал раскрашивать функции в нашем коде :)) Ох сколько там неявных зависимостей вылезло через 100500 слоёв, которых вообще не ожидаешь. Даже пришлось ввести специальный цвет "растворитель", который, смешиваясь с любым, превращает его в прозрачный (AnyColor + remover = transparent). В статье этого не упоминал, но в доке обозначил.

Дельный комментарий.

На часть пунктов ответил автор выше.

По поводу сторонних библиотек — да, всё верно. Практическую сторону это омрачает. Но в плане самой идеи ничего не меняется, а делился я в первую очередь ей :)

Спасибо за ответ. Только добрался до комментариев. Всё верно :)

Похоже, что ты не передаёшь имя главного файла или он не существует. Типа, вызываешь просто `kphp`, а не `kphp some_file.php`.
Оказывается, тогда мы падаем некрасиво — сами не натыкались, т.к. у нас во внутренних скриптах всегда имена корректные )) Но вот уже поправили, чтоб писал нормальную ошибку — https://github.com/VKCOM/kphp/pull/5, видимо ровно оно :)
Можно на каком-нибудь мелком примере.
Например, безобидный PHP-шный foreach:
foreach (getArr() as $k => $v) { ... }


В C++ нужно: объявить переменные $k, $v; объявить временную переменную, куда присвоится вызов функции getArr(); сделать цикл через for по итератору, внутри этого цикла присвоив в $k и $v ключ и значение из этого итератора; не забыть, что в общем случае если вместо getArr() какая-то локальная переменная, то этот массив может меняться внутри тела цикла (в PHP это норм практика), поэтому с итераторами там тоже не всё банально; не забыть уменьшить рефкаунтер после цикла, т.к. туда дополнительно ссылается временная переменная.

В итоге этот foreach превращается в весьма громоздкую конструкцию, и это нормально, потому что нам нужно покрыть все возможности PHP массивов.

И уж точно лучше не заглядывать в то, что получается из корутин и прерываемых конструкций )
1

Information

Rating
Does not participate
Location
Санкт-Петербург, Санкт-Петербург и область, Россия
Works in
Date of birth
Registered
Activity