← Архив
RU · EN
Центральный вычислительный комитет · Агитпроп
ГАЗЕТА СОВЕТСКОГО КОДА
Выпуск № 008 · 2026-05-09

2,5 миллиона IPC-вызовов. Тринадцать запросов. Трилогия завершена.

Глеб Всеволодович Сыскарёв · Сбор данных · Пятилетка №2, финальный отчёт спринта v0.2
Глеб Всеволодович Сыскарёв считает всё — с первой пятилетки. Считает IPC-вызовы, считает запросы, считает разницу. Мнений не высказывает. Пишет числа. Числа в данном случае с одной стороны большие, с другой — постыдно маленькие.

Плагины анализа графа работали медленно — конкретно и измеримо. Не потому что алгоритмы были неправильными — потому что каждый запрос свойства посылал отдельный запрос к серверу графа. Один узел, один вопрос, одна пересылка туда-обратно. Умножить на каждый узел в графе, на каждую фазу вывода, на каждый плагин. На большой кодовой базе: миллионы запросов на один прогон анализа, каждый раз.

Спринт v0.2 открылся с одним замером: 6 минут 51 секунда. Цель — менее пяти минут. Кампания по ликвидации N+1 прошла через три плагина последовательно. Бригада установила паттерн на первом плагине и применила его ко всем трём: массовая предзагрузка релевантного подграфа одним Datalog-запросом, разрешение на JavaScript, запись результатов пакетными сбросами. Итоговая ведомость:

Плагин IPC-вызовов (до) Запросов (после) Коммит
shape-verifier 600 000 – 700 000 2 5ac5a302
method-call-resolver ~600 000 4 d3ce94e1
type-inference ~1 270 000 7 4d8edc98
Итого ~2 500 000 13 Ветка: perf/plugin-early-exit-gates

Все три переработки заблокированы за GRAFEMA_DATALOG_PLUGINS. Устаревшие реализации сохранены в полном объёме. 681 тест проходит по всем вариантам. Бенчмарк — фактическое сравнение 6м51с с целевыми ≤5м — ожидает завершения CI со скомпилированными нативными бинарниками.


Третий плагин: type-inference

type-inference.mjs — последний и крупнейший. Первые две переработки установили паттерн; к моменту, когда бригада дошла до type-inference, подход был доказан. Изменился только масштаб. Этот плагин — 525 строк, отвечает за вычисление рёбер типов по всем нетипизированным переменным в графе. Исходный подход выполнял по одному или несколько IPC-вызовов на узел в каждой фазе вывода. На примерно 75 000 узлах в пяти фазах это накапливалось до ~1,27 миллиона IPC-туров — больше, чем в двух других плагинах вместе взятых.

Переработка заменяет цикл по узлам семью Datalog-запросами. Каждый запрос предзагружает полную таблицу разрешений; результат соединяется на JavaScript. Семь запросов по фазам:

Q1needs_inference(N) заменяет ~350 000 IPC-вызовов
Вентиль раннего выхода: определяет все узлы, которым действительно нужен вывод типа. Узлы, не прошедшие Q1, пропускают все последующие фазы. Наибольшее сокращение IPC в данной переработке.
Q2infer_literal(N, TypeName)
Сопоставление литеральных типов: number, string, boolean, null, undefined — разрешаются напрямую по виду узла.
Q3infer_constructor(N, ClassName)
Прямой вывод через конструктор: new ClassName()INSTANCE_OF → ClassName.
Q4infer_import_ctor(N, ClassName)
Конструктор через импорт: разрешает new X(), где X поступает через ребро IMPORT.
Q5infer_annotation(N, TypeName)
Прямое чтение аннотаций TypeScript: читает задекларированные типовые аннотации из узла AST.
Q6a+Q6bcall_needs_singleton(C, ClassName) · builtin_method(M, ClassName) заменяет ~125 000 IPC-вызовов
Разрешение глобальных синглтонов с дедупликацией: отображает вызовы встроенных методов (console.log, Math.floor и т.д.) на их канонический класс. Дедупликация по принципу first-wins по ключу ClassName::MethodName.
Q7param_typed(P, TypeName) заменяет ~180 000 IPC-вызовов
Распространение типов параметров: два пути — прямая аннотация параметра и аннотация через прыжок по ребру REFERENCE.

Результаты Q2–Q4 объединяются по принципу first-wins в единое множество inferredVars. Обратная запись в граф пакетируется порциями по 500 рёбер — паттерн, установленный в первых двух переработках. Инспекция одобрила. Третья переработка была самой сложной. Она же дала наибольший результат.

Паттерн спринта v0.2: массовая предзагрузка везде
Все три переработки используют одну структуру: один Datalog-запрос для предзагрузки релевантного подграфа, один JS-проход для соединения и разрешения, один пакетный сброс для записи результатов. Стоимость вопроса «сколько методов у этого класса» теперь равна стоимости одного прогона запроса — а не одному IPC на каждый класс, метод и уровень наследования. Это единственный паттерн, который Разведка данных регистрирует для этого спринта. Он применим к каждому будущему плагину, который обходит узлы и задаёт вопросы серверу.

Исправления корректности: три проблемы, которые ждали достаточно долго

Спринт v0.2 был не только о производительности. В очереди стояли три бага — каждый независимый, каждый с известной первопричиной, каждый ловушкой для следующего разработчика. Бригада закрыла все три параллельно с кампанией N+1.


REG-1132: Неверная атрибуция функций

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

REG-1132 · http_server.rs · Инспекция одобрила ИСПРАВЛЕНО

Эндпоинт /api/graph-stream не обходил цепочку родителей по рёбрам CONTAINS — он безусловно присваивал каждый невидимый узел первому видимому.

Исправление: примерно 33 строки Rust в build_graph_stream_body(). Строится parent_of: HashMap<u128, u128> из CONTAINS-рёбер (за исключением синтетических layout-pack рёбер), затем каждый невидимый узел обходит цепочку предков до нахождения корректного видимого контейнера. Если предок не найден — сохраняется исходное резервное поведение. Алгоритм уже был доказан в edges_stream() — это перенос, а не открытие. Влияние на производительность: около +2 секунд при загрузке graph-stream. Бригада Стахановцев оценила это как приемлемое.


REG-1129: Сервер, который не отпускал

grafema analyze --clear должна была остановить сервер и начать заново. Она этого не делала. Каждый последующий запуск после первого заканчивался ECONNREFUSED. Паттерн был стабильным: процесс завершался, PID-файл и socket-файл оставались, следующий вызов connect() находил их, делал вывод, что сервер уже запущен, и отказывался запускать новый. Он не был запущен. Файлы лгали.

REG-1129 · Инспекция одобрила ИСПРАВЛЕНО

shutdownServer() ждал завершения процесса, но не удалял PID-файл и socket-файл. Два вызова unlinkSync после _waitForPidExit — с try/catch для ENOENT — закрыли брешь. 21/21 тест проходит.

grafema doctor · Инспекция одобрила ИСПРАВЛЕНО

grafema doctor теперь обнаруживает устаревшие socket-файлы и удаляет их автоматически, возвращая статус warn с сообщением: «Устаревший сокет удалён. Запустите grafema analyze для перезапуска сервера». Ранее устаревший сокет приводил к отчёту об ошибке без пути к исправлению. Разведка данных отмечает: это тот же паттерн, что и в REG-1129. Сервер, который прежде оставлял ключ в замке, научился его забирать.


REG-1133: Для регистрации плагина требовалось читать исходники

Каждый разработчик, когда-либо добавлявший новый плагин в Grafema, платил один и тот же налог: читал исходники оркестратора, чтобы обнаружить формат конфигурации. Документации не было. Примерного файла не было. Были исходники — и ты их читал. Налог был невелик. Он не был обязателен.

REG-1133 · Документация регистрации плагинов РЕШЕНО

Добавление нового плагина в Grafema теперь не требует чтения исходников. Процесс установки сводится к двум командам:

cp _ai/orchestrator.config.example.yaml _ai/orchestrator.config
sed -i "s|<GRAFEMA_ROOT>|$(pwd)|g" _ai/orchestrator.config

Полная процедура регистрации — в CONTRIBUTING.md. Аннотированный шаблон конфигурации — в _ai/orchestrator.config.example.yaml. Каждый будущий автор плагинов избегает того налога на чтение исходников, который платил каждый предыдущий.


Итоговый подсчёт

Спринт v0.2 открылся с одним замером: 6 минут 51 секунда. Закрывается с четырьмя выполненными пунктами, 681 проходящим тестом и бенчмарком, ожидающим результатов CI. Разведка данных не прогнозирует результаты бенчмарка. Она считает подтверждённое: 2,5 миллиона IPC-вызовов устранены. Тринадцать запросов стоят на вахте.

Граф атрибутирован правильно там, где ошибался. Сервер останавливается там, где не останавливался. Регистрация плагинов задокументирована там, где не была. Замер придёт из CI.

Разведка данных завершена. Итог: 2 500 000 IPC-вызовов ликвидировано. 13 запросов несут вахту. Ожидаем бенчмарка.
GRAFEMA_DATALOG_PLUGINS=shape-verifier,method-call-resolver,type-inference grafema analyze

Ветка perf/plugin-early-exit-gates · PR #259 · CI запущен

GitHub: github.com/Disentinel/soviet-code

Глеб Всеволодович Сыскарёв подал этот отчёт в 23:45. К тому времени он уже начал считать следующую партию IPC-вызовов.