zero deps
57 subscribers
1 photo
27 links
Node.js, V8, highload, архитектура.
Где производительность, а где оверинжиниринг?

Код, профилирование и воспроизводимые бенчмарки.

Всё, что ставим — потом и поддерживаем.
Фиксим то, что сами и сломали.

zerodeps.tech
Download Telegram
Да, это ещё один инженер с мнением

Ночь, комната, макбук, проекты. За последние несколько лет для меня это стало нормой. Хотя все и говорят, что нельзя вести такой образ жизни. Но для меня в этом всё ещё остаётся некоторая доля романтики. Ближе к ночи, окружение затихает, перестают постоянно писать в мессенджеры, город засыпает. Просыпается мафия, и я. 

Ближе к полуночи в обнимку с макбуком и чайником какого-нибудь китайского чая, погружаешься в проект, ловишь поток и пропадаешь из реальности на несколько часов. Отдаёшь себя полностью этим строкам кода, схемам, паттернам, архитектуре. Мне кажется, именно ночью, я находил ключевые решения бизнес задач, которые в последующем приносили мне и компании иксы в прибыли. Именно в это время, как мне кажется, я растил хард скилы, софт скилы, мышление и усталость. Для меня это особенное время. А к чему это я? 

Да так, захотелось поделиться, и думаю настало время познакомиться, наконец.

Думаю, ты, читатель, задавался вопросом, что это за хуй, и почему он пытается мне втереть всю эту дичь. 

В целом вопрос резонный. Я не хотел бы рассказывать тебе, какой я красивый, умный и вообще пивдатый. Это всё не так, автор априори, туп, глуп, ничего не смыслит в разработке и архитектуре. Но за последние несколько лет я немного понахватался всякого и повидал разного. Построил пару систем, познакомился с ияй, плотненько погрузился в архитектуру и работу в стезе platform инженера. 

Я топлю за zero deps подход, наверное, ты уже догадался. Но не только из-за безопасности, нет, это только верхушка. Zero-deps даёт контроль и возможность выжать максимум. Максимальный Performance это то, к чему я стремлюсь. Если что-то сделать можно более эффективно, постараюсь это сделать. 

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

Считаю, что, если задачу можно решить просто, быстро и без зависимостей, то так и стоит сделать. Не стоит тащить туда инструменты ради инструментов, особенно если это увеличивает плечо разработки.

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

На данный момент из публичного, это microlike фреймворк поверх uWebSockets, изучаю оверхед, который приносит nodejs и как дорого приходится платить за удобство. 

За эти годы мне довелось поработать с довольно разными системами, от высоконагруженных микросервисов на ноде и realtime сервисов до инфраструктуры, протоколов, автоматизации и продуктовых платформ. Возможно, когда-нибудь смогу рассказать подробнее ;) 

А ещё у меня есть большой оранжевый джип, любимая жена и кот. Люблю читать фантастику и собирать лего, и как всё играю в компуктерные игры. 

Жена, привет, знаю, ты это прочитаешь. Спасибо и тебе, читатель. 

Кушайте кашу и читайте книги. Хейтеры, обнял вас.
8🔥4
Хреновый ты архитектор, zero deps

Привет, читатель, случилась недавно со мной интересная ситуация.

Пришёл ко мне знакомый, назовём его З, от знакомого Л, и говорит: «zero deps тыж программист, а не сможешь ли ты, мне, пожалуйста, сделать ТГ ботика для учёта моего мелкого бизнеса, за денежку».

Кто я такой, чтобы отказать.

Говорю, не вопрос, любой каприз за ваши деньги, давай подробнее: «Что? Как? Зачем? И почему?»

Обсудили, если коротко: хочется ТГ бот учёта движения деняк в гипермалом бизнесе для самоконтроля. Гугл таблицы надоели, данные «открыты», там не синкается, здесь тормозит, там доступ отвалился. С телефона неудобно, нужен бот.

Бюджет не пилим, сошлись на том, что делаю безвозмездно, а там «спасибо» на усмотрение Товарища З.

Минут 20 покумекал и сложилось ТЗ с примерным планом-капканом и стеком.

План простой: берём термос чая, минут 40 ияй собирает пару визардов и пачку клав на grammy для бота и базовый юзкейс. К этому всему прикручиваем Pg с четырьмя табличками. Тратим часик — полтора на рефакторинг говнокода от ияй — и в продакшен. Живое MVP часа за 3 и термос чая.

Заспичил Товарищу З, сошлись во мнении, что нужно с этим переспать.

Я сплю, а мысль не отпускает: потребность и «хотелку» товарища закроем, а первопричина боли останется. ТГ, имхо, такое себе для учёта. Что-то записать простое и мелкое сойдёт, но чуть большие масштабы не потянет: аналитику, таблички, репорты, и куча всякого ещё будет сложно красиво и удобно нарисовать. Нужна веб-морда.

Накидал ТЗ к веб-морде, сижу, гляжу — вырисовывается excel на минималках без куртизанок и блек-джека. Ясно вижу велосипед на костылях. Но помочь человеку хочется.

Заспичил Товарищу З, снова сошлись на том, что нужно переспать.

Лежу, думаю дальше: зачем так сложно, давай развернём селфхостед. Возьмём какой-нибудь миниайо ака S3 или вообще nextcloud, прикрутим туда onlyoffice как редактор. И получим на выходе свой гуглдиск со своим гуглдоком и гуглщитом. Сотрудникам доступы красивые раздадим, будет учёт «практически excell» с блек-джеком и резиновыми куртизанками.

Проснулся, заспичил Товарищу З селфхостед сверху к боту и веб-морде. Сошлись на том, что нужно переспать. Но веб-морда всё же ближе, а лучше бот, так как не таблицы и бот хочется.

На следующий день поехал к товарищу Л за рюмкой чая, его работу работать.

А товарищ Л, по совместительству, ещё и широко известный в узких кругах крутой техлид, опытный разработчик и человек, который всему меня научил. Мудрый бвана в общем.

Пожаловался ему, поплакался, что вот у меня дилемма: хочется ботика в тг, а можно веб-морду, а ещё можно селфхостед.

И каких же мне в панамку напихали.

Мудрый Бвана сказал простую вещь: архитектор должен решить первопричину боли.

А первопричина боли товарища — в том, что данные «открыты» и «таблички не синкаются».

Тг бот был сразу отвергнут, безапелляционно, залупа.

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

А в финале было предложено сто процентное решение — excel. Да, тот самый, от мелкомягких, с облаком ихейным.

Я тогда со своей панамкой домой уехал, написал Товарищу З, что действительно будет лучше ему в excel погрузиться. А мы с Товарищем Л ему всячески поможем с этим.

Как-то так.

Читайте книги, ищите первопричину боли и кушайте кашу.

P.S. Все персонажи вымышленные. Все совпадения случайны. Все вероятности невероятны.

А Товарищ З себе ботика в итоге навайбкодил за вечер :)
🔥8
О пользе отдыха для чукчи

Чукча не дурак, чукча иногда пытается отдыхать от этих ваших программирований. Иногда у него это даже получается. 

Всё началось не так давно, лет шесть назад. Я молодой и амбициозный, готовый работать за еду, прилетаю в приморский город N. Ничего не предвещало беды, но меня берут на работу в небольшой стартап, обещают кормить, учить и немного деняк. Что, может пойти не так? 

В целом всё отлично, я каждый день как штык в офисе, каждый день штудирую learn javascript, воюю на codewars и решаю задачи от мудрых и старших комрадс. Всё бы ничего, но к концу месяца мне говорят, ты себя крут показал, мы тебе заплатим х2, продолжай в том же духе. 

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

Через пару месяцев начались рабочие задачи. Стало ещё интереснее и веселее. Учёба теперь была в перерывах между решением рабочих задач и сном. Рабочие задачи несли ещё больше новых знаний. Я не знал усталости, я не говорил слов: «не знаю», «не получается», «наверное, я не смогу». На все вопросы и предложения я отвечал: «Сейчас узна́ю», «сейчас изучу», «сейчас придумаю решение», «да, конечно, без проблем, сейчас решим». И это давало свои плоды. Знания и опыт ширились, доход стремительно рос, я развивался как специалист. Но было, одно "но". 

Этим "но" был отдых. Для меня отдыхом было прочитать пяток статей на тему, как что-то написать, или как что-то работает. Отдыхом для меня были попытки придумать и написать для себя: crm, арбитражного бота, сайтик для бронирования уроков по английскому, бесчисленное количество ботов в тг. Я думал, что отдыхаю, когда изучал, как работает браузер, что за зверь такой этот фингерпринт и зачем его собирают. Думал, что мой мозг переключается при работе не с js, а с плюсами, когда я копался в хроме с электроном. Ой, как я ошибался. 

Незаметно для меня пролетели 4 года и пришло оно, выгорание. Ой, ты, читатель, наверное, сейчас начнёшь в меня кидаться говной и кричать, о блет ещё один псевдоразрабочик, типа заебался и выгорел, нытик блет. Ну я в целом тоже так думал. Какое нахер выгорание, тебе ещё 30 нет, ты здоровый молодой, парень, тебе ебашить и ебашить! Ну как бы да, но и не то чтобы нет. 

События помимо работы тоже оставляли свой след. Помимо того, что ты ебашишь 24/7, так тебе ещё пиздюлей ментальных, моральных, а иногда ещё и физических старается навесить окружающий мир. Это тоже давало свои плоды. Кукуха решила сказать «Адьес» и ебануть во все стороны, как твой кот, перед тем как сходить посрать. 

Ну вот примерно тут я попробовал уйти с текущей работы и плотно заняться собой, здоровьем, кукухой и попробовать отдохнуть. Но и тут, что-то пошло не так. Меня купили, мне предложили неприлично много, и я не смог отказаться. Спасибо моей Жене, которая в тот момент пришла и ультимативно заявила, что хватит это терпеть, тебя тяжело вывозить с твоей кукухой. Ты пиздуешь к психологу, ты ебашишь в зал с тренером и ты, мать твою, делаешь себе хотя бы один выходной в неделю, пидр!

Спасибо тебе, Жена любимая. Ты была права! Я уже полгода замечал за собой, что производительность моя и импакт в работе снизился. Я не мог себя удовлетворить достигнутыми результами при решении задач. Я всё ещё приносил пользу компании, но уже не мог принести пользу себе. И тут чукча сдался.
🔥6
Вот уже два года чукча, в лице вашего автора @zerodeps, учится отдыхать. Это оказалось не так-то просто. Это оказалось ппц непросто. Казалось бы, забей хуй, валяйся и читай книгу, ходи по улице или в компьютер поиграй. Но нет, мой коварный мозг постоянно вставляет палки в колёса и пытается навязать мне разные чувства. От мысли, что ты бездарный бездельник, потому что уже 5 минут не проверял рабочий чат, до чувства дичайшего дискомфорта, когда у тебя в грёбаном нигде посреди леса пропадает связь и ты не можешь загрузить чаты в тг. Ещё есть чувство упущенных возможностей, это вообще отдельная тема. Таких примеров я могу привести, наверное, десятки, если не сотни. Возможно, когда-нибудь напишу об этом подробнее. 

Два года учусь отдыхать, переключаться и посылать этот мир на хер. Это повышает мой кпд, делает мою работу приятнее. С каждым днём у меня получается отдыхать чуточку лучше. Чего и вам советую ;)

Отдыхайте, кушайте кашу и иногда не читайте книги!
🔥5
О том как Чукча свой путь ходит

Буквально каждый человек, который сталкивался с тревогой и всяким таким рядом, слышал о том, что нужно ходить. Да-да, ходить ногами в разные стороны, и это поможет в борьбе с ментальными проблемами и физически полезно. Чукча, @zerodeps, попробовал. И вот как это получилось.

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

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

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

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

Как раз подошёл к завершению час ходьбы, спасибо, что читаете.

Как-то так.

Кушайте кашу, ходите и читайте книги
👍3🔥2
Объекты в JS не бесплатные

Hidden classes, inline cache и почему shape объекта важнее, чем кажется.

Продолжу про микро-оптимизаций. После Date.now() и parseInt/parseFloat — про, казалось бы, самую безобидную вещь: обычный {}.

Сразу оговорка: для 99% систем это не имеет значения. А вот если у тебя hot path, через который проходят десятки тысяч объектов в секунду, или ты пишешь библиотечный код, стоит знать, что V8 на самом деле делает с твоими объектами.

И чего он там с ними делает?

Когда ты создаёшь объект, V8 не выделяет «мешок ключей». Он строит для него hidden class (map внутри V8) структуру, которая описывает layout: какие у объекта поля, в каком порядке они лежат, какого типа, по каким офсетам читать. Сам объект это компактный массив значений, как struct в C. Hidden class хранится отдельно, и каждый объект держит на него ссылку.

Hidden classes связаны между собой через transitions и вместе образуют transition tree. Когда ты добавляешь поле к объекту, V8 идёт по исходящему переходу из текущего hidden class: если переход с таким полем уже есть переключается на существующий дочерний, если нет создаёт новый hidden class и новую ветку дерева.

Если несколько объектов идут по одним и тем же переходам в одинаковом порядке они разделяют один hidden class. Это даёт V8 базу для inline cache: «по этому офсету всегда лежит string, читаем напрямую, без проверок».

Когда hidden classes начинают расходиться (по порядку, по типам, по добавлению/удалению), inline cache переходит:
monomorphic (один shape — fast path),
polymorphic (до 4 shapes — V8 проверяет каждый, всё ещё быстро),
megamorphic (4+ — V8 сдаётся, идёт через generic lookup).

Стоимость растёт на каждом шаге.

Где ты теряешь?

Классический пример постепенная сборка vs литерал:


const a = {}
a.id = 1
a.name = 'jopa'

const b = { id: 1, name: 'jopa' }


Если порядок добавления полей совпадает, оба варианта в итоге попадают на один hidden class. У литерала есть бонус V8 запоминает финальный shape как boilerplate и при повторных вызовах аллоцирует объект сразу с готовым layout, без прохода по transition tree. Но в hot loop разница в установившемся режиме копеечная.

Хуже, если порядок полей в разных фабриках не совпадает:


function makeA(id, name) { return { id, name } }
function makeB(name, id) { return { name, id } }


Это уже два разных hidden class. Любой код, который читает obj.id через эти фабрики, видит два shape на одном call site и IC становится polymorphic. Само по себе ещё не больно, но если таких разных объектов не два, а шесть, то добро пожаловать в megamorphic.

Ещё кейс условное добавление полей:


const user = { id, name }

if (isAdmin) {
user.role = 'admin'
}


Часть объектов имеет shape {id, name}, часть — {id, name, role}. Уже polymorphic. Лучше класть role: null сразу и потом присвоить значение при необходимости.

delete — отдельная история


delete user.name


Вот тут V8 уже не прощает. delete переводит объект в dictionary mode (он же slow mode, normalized properties). Это hashmap вместо фиксированного layout. Ни inline cache, ни офсетов, каждое чтение поля идёт через hash lookup в C++ runtime. И обратно V8 объект уже не вернёт, даже если форма стабилизировалась.

Вроде как память освободил. А по факту превратил объект в Map без типизации.

Если поле больше не нужно лучше присвой null или undefined. Shape сохранится, движок не потеряет в оптимизации.
🔥3
А что по цифрам?

Стенд: Node 24, миллион объектов с тремя полями, в hot loop читаем obj.id, меряем ns/op. По три прогона, чтобы JIT успел устаканиться.

— literal (same shape): 3.99 нс
— stepwise (same order): 4.08 нс
— poly IC (2 shape): 4.65 нс
— megamorphic (6 shapes): 8.51 нс
— dict mode (after `delete`): 56.12 нс

Что видно:
literal vs stepwise — разницы практически нет. V8 одинаково хорошо справляется с обоими.
polymorphic IC (2 shape) — ~15% оверхед. V8 спокойно тянет до 4 shape на одном call site.
megamorphic (6 shape)x2+ к чтению. Уже серьёзно.
delete (dictionary mode)x14 медленнее. Больно :)

На 100k RPS × 50 чтений полей на запрос с dict-mode объектами вместо нормальных — потеря ~260 мс CPU/с.

А что было на Node 22?

Для наглядности — те же сценарии на Node 22:

— literal: 11.0 → 4.0 нс (x2.8)
— stepwise: 11.1 → 4.1 нс (x2.7)
— poly (2 shape): 10.3 → 4.7 нс (x2.2)
— megamorphic: 14.1 → 8.5 нс (x1.7)
— dict mode: 56.8 → 56.1 нс (=)

Что интересно:

Fast path между релизами стал ~x2.5 быстрее. TurboFan и Maglev продолжают тюниться, монохромное чтение поля на Node 24 — 4 нс против 11 на Node 22.
Dict mode — константа. 56 нс на обеих версиях. Это C++ hashmap lookup, JIT там не играет, оптимизировать нечего.
Относительная разница fast/slow растёт. На Node 22 delete был x5 медленнее literal'а, на Node 24 — уже x14. Fast path уходит вперёд, slow path стоит.

Вывод: с каждым новым V8 сидеть в dictionary mode или megamorphic IC становится всё дороже _относительно_ правильного кода.

А что делать то?

– В hot path — литералы, со всеми полями сразу. Не знаешь значение полож null.
– Поля во всех фабриках — в одном порядке. Делаешь { id, name }, делай везде { id, name }.
delete — не нужон. Только obj.field = null (или undefined).
– Если объект сложный и часто создаётся — класс или фабрика. Один shape гарантированно.
– Не стоит превращать один call site в универсальную точку на все случаи жизни: четыре shape потолок V8, дальше generic.

Что в итоге?

Объект в JS не «просто словарь». Это контракт с V8: ты обещаешь стабильный shape, V8 обещает быструю работу через hidden classes и inline cache. Нарушаешь — съезжаешь в polymorphic, потом megamorphic, потом dictionary mode. С каждым шагом дороже.

В обычном CRUD это не заметно. На hot path — это десятки процентов CPU и заметные хвосты по p99.

Date.now() тратил CPU на syscall, parseFloat — на парсинг, плохой shape — на cache miss. А потом говорят: "ой да ну какой Node, он же медленный, давайте напишем на Go и пойдем за ванильным лате на растительном"

Node быстрый. Просто не мешай ему.

Что еще почитать?
V8 internals: hidden classes & inline caches — Mathias Bynens, must-read
Fast properties in V8 — официальный блог V8
Elements kinds in V8 — про массивы и их shapes
🔥3
Forwarded from PiterJS (Дим)
⭐️ На грядущем PiterJS для нас выступит Василий Каменюк с докладом:

😱 Объекты в JS не бесплатные
Node на самом деле быстрый, просто, по возможности, не стоит мешать ему работать.

Почему Delete может снизить перфоманс? Почему порядок полей в объекте не стоит делать разным? И другие вредные советы как сделать жизнь V8 проще при работе с объектами.

Микрооптимизации для работы с объектом, hidden classes, inline cache и почему shape объекта важнее, чем кажется.


👉 Регистрируйся, пока есть места, а если передумал идти — отмени регистрацию там же.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥118
Как чукча решил перед людями выступать

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

Вот я и был одним из таких людей.

Началось всё давно, когда я ещё даже программистом то не был. Лет 6–7 назад, в один из своих отпусков, я прилетел в солнечный февральский Санкт-Петербург.

До кардинальной смены деятельности оставалось всего полгода. Но мне уже хотелось прикоснуться к прекрасному — к IT-комьюнити.

Мои поиски увенчались успехом, и я, счастливый, что всё так удачно сложилось, посетил PiterJS.

Как сейчас помню: крутейший офис JetBrains рядом с Питерлэндом. Я сидел там, слушал доклад про новомодный тогда Svelte и думал:

«Как круто, наверное, вот так шарить в теме. Быть крутым специалистом. Не просто знать и использовать, но ещё и рассказывать об этом людям. Выступать со сцены перед полным залом, и чтобы эти люди тебя ещё и слушали.

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

Тогда я получил неимоверный заряд мотивации. Ещё и годовую подписку на WebStorm выиграл.

И, как мне кажется, уже тогда я себе пообещал: я попробую стать таким же крутым, как эти умные люди в очках, чтобы однажды тоже что-то умное втирать со сцены, а меня слушали.

Через полгода я уже учился писать на Node.js за «еду» с утра и до заката. А мне за это ещё и платить начали.

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

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

А мне всё ещё за это платили.

И вот мне 30. Около 6 лет работы напихали в мою голову такой объём знаний, что им потребовался выход.

Я начал записывать мысли. Сначала просто в заметки, без каких-либо планов. Потом попробовал оформлять эти мысли в статьи и показывать людям.

Так появился канал.

Поначалу он задумывался просто как технический блог про архитектуру и zero deps. Но со временем получилось что-то большее. Личный дневник, как мне кажется.

Что-то я отвлёкся.

Так вот. Мне 30 лет, я пробую вести блог, продолжаю исследовать Node.js и хочу найти новую работу. Ничто не предвещало беды. Но идиллия не может быть вечной.

После очередного митапа, уже на афтапати, мой друг О. говорит:
«Твой последний пост — огонь. Надо с ним выступать. Срочно подавай заявку».

И этим же вечером моя любимая супруга говорит то же самое:
«Я тебе уже несколько лет говорю. Вот и материал у тебя есть, пост огонь, пора».

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

Умным я не стал, но пост действительно получился интересный.

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

И вот, спустя каких-то 6–7 лет, я стою на том месте, где выступали умные люди и рассказывали умные вещи. Рассказываю залу что-то умное про Node.js.

И меня в зале даже, кажется, слушают.

Считаю ли я это достижением? Определённо.
Хочу ли я выступать ещё? Однозначно.
Считаю ли я себя «умным человеком в очках»? Пока ещё нет.

Можно ещё многое написать: про страх, про то, как проходило выступление, и какие выводы я сделал. Но оставлю это на потом. Возможно, расскажу в следующих постах.

А пока кушайте кашу, читайте книги и стремитесь стать «умным человеком в очках».
1🔥95❤‍🔥2
Смена типа тоже не бесплатна

Привет, читатель. Давненько из меня не выходило контента. Пора исправляться. Принёс для тебя дополнение к прошлой статье про объекты.

Я разбирал hidden classes, inline cache и форму объектов. Главный практический совет был простой: если поле опциональное, создай его сразу и положи null, чтобы все объекты сохранили одинаковую форму.

Совет рабочий, но неполный. Форма объекта у V8 может остаться прежней, а контракт поля всё равно изменится. И тогда горячая функция тоже поедет на деоптимизацию.

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

Map знает не только форму

У каждого объекта в V8 есть Map, он же hidden class. Map описывает набор полей, их порядок и смещения. Но в дескрипторе поля хранится ещё и его внутренняя representation, то есть способ, которым V8 ожидает хранить значение.

Упрощённо интересующая нас цепочка выглядит так:


Smi маленькое целое, закодированное прямо в tagged-слоте
Double числовое значение с плавающей точкой
HeapObject ссылка на объект в куче: строку, объект, null, undefined и так далее
Tagged широкое представление, которое принимает и Smi, и ссылки


Это внутренний контракт хранения. В самом V8 отдельно существует ещё и field type, но для перехода из number в string нам достаточно representation.

Когда V8 много раз видит { x: 42 }, поле x может получить representation Smi.

Оптимизатор по известному Map читает поле как маленькое целое и строит код под этот контракт.

Потом кто-то делает так:


obj.x = 'boom'


Строка уже не помещается в Smi. V8 расширяет representation до Tagged, потому что теперь поле должно принимать и целые числа, и ссылки на объекты в куче.

Ключевой момент: это не обязательно создаёт новый Map. Для некоторых расширений V8 обновляет дескриптор на месте, и адрес Map остаётся тем же. Но оптимизированный код зависел не только от адреса Map, а ещё и от representation. Контракт изменился, зависимый код больше нельзя считать безопасным для использования.
🔥2
Вот сам деопт

Минимальный пример на Node 24.17.0 с V8 13.6:


function readX(object) {
return object.x
}

const a = { x: 1 }
const b = { x: 2 }

// Убираем отдельную зависимость от constness поля.
a.x = 3
b.x = 4

for (let i = 0; i < 100_000; i++) {
readX(i & 1 ? a : b)
}

// --allow-natives-syntax
%OptimizeFunctionOnNextCall(readX)
readX(a)

a.x = 'boom'


Запускаем:


node --allow-natives-syntax --trace-deopt demo.js


И получаем:


[marking dependent code ... (readX) for deoptimization,
reason: dependent field representation changed]


То есть одна запись строки действительно инвалидировала уже оптимизированную readX.

В этой версии V8 %HaveSameMap(a, b) остаётся истинным даже после присваивания строки. Форма та же, Map тот же, а деоптимизация всё равно случилась.

%OptimizeFunctionOnNextCall и %HaveSameMap являются внутренними средствами диагностики V8. В прод их, разумеется, тащить не стоит.

Где здесь подвох с `null`

Вернёмся к совету «создай поле заранее и положи `null`».

Для формы объекта он по-прежнему полезен:


const user = { id, name, role: null }

if (isAdmin) {
user.role = 'admin'
}


Поле role есть у всех объектов с самого начала. Кроме того, и null, и строка относятся к HeapObject, поэтому именно representation при такой записи расширять не требуется.

С числом история другая:


const stats = { score: null } // HeapObject
stats.score = 42 // нужно принять ещё и Smi, получаем Tagged


Если код успел оптимизироваться между этими двумя состояниями, расширение поля может инвалидировать зависимую оптимизацию.

Для целочисленного поля стоит сразу дать целочисленное значение:


const stats = { score: 0 }
stats.score = 42


С undefined та же проблема, что с null: для V8 это объект в куче, а не числовая заглушка.

Но не стоит превращать это в очередной карго-культ. Если null является честным состоянием бизнес-модели, оставь null. Семантика программы важнее одной потенциальной деоптимизации на прогреве.

А если поле хранит дробные числа, одного 0 тоже недостаточно для идеальной стабильности: переход от Smi к Double способен потребовать ещё одно расширение.

Идеальный вариант для горячей структуры: присвоить реальное значение до того, как код станет горячим.

Расширение происходит не на каждом присваивании

Здесь легко сделать неправильный вывод: будто каждое переключение 42 → 'boom' → 42 снова деоптимизирует функцию из-за representation.

Нет. Representation расширяется до Tagged один раз и самостоятельно обратно до Smi уже не сужается. После этого и число, и строка помещаются в установленный контракт. Повторной генерализации поля на каждом переключении нет.

Деопты всё ещё возможны, если горячий код специализировался на фактических значениях. Например, выражение object.x + 1 сначала выполняет числовое сложение, а со строкой начинает конкатенацию.

Это уже другая причина: обратная связь по типам самой операции плюс возможные аллокации строк. Сваливать всё это в одну корзину с field representation удобно только до первого человека, который полезет в --trace-deopt.

Поэтому я не буду продавать здесь красивое «переключение типов замедляет цикл ровно в два раза». Такой микробенч легко измеряет одновременно чтение поля, смену representation, арифметическую специализацию, конкатенацию и повторную оптимизацию. Цифра получится эффектной, но инженерного смысла в ней будет примерно нисколько.

Защищаемый вывод уже есть в трейсе: первое расширение representation способно инвалидировать весь оптимизированный код, который от неё зависит. Цена конкретного деопта зависит от функции, нагрузки, версии V8 и того, сколько зависимого кода придётся пересобрать.
🔥1
Что делать-то?

Да, в целом ничего :)

Это просто интересные факты про Node.js, так же, как и про Map в прошлой статье.

Но если ты оптимизациофил, то:
- Создавай горячие объекты с полным и стабильным набором полей.
- Для целочисленного поля используй числовое начальное значение, а не null или undefined.
- Присваивай реальное значение до прогрева, особенно если поле может хранить дробные числа.
- Не смешивай число и строку в одном поле без причины.
- Проверяй подозрения через --trace-deopt.

Что в итоге

Одинаковая форма объекта ещё не гарантирует, что оптимизированный код останется валидным. V8 учитывает representation полей и может встроить это знание в машинный код.

Переход number → string дорог не потому, что строка сама по себе медленная. Он опасен в момент, когда ломается контракт уже скомпилированной функции. V8 расширяет поле, помечает зависимый код на деоптимизацию и затем работает с новым, более широким представлением.

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

JS разрешает нам менять типы как угодно. V8 тоже не против. Но лучше не менять.

Кушайте кашу, читайте книги и не меняйте типы без необходимости.

Что почитать

Fast properties in V8, официальный разбор Maps, дескрипторов и быстрых свойств
Representation в исходниках V8, актуальная внутренняя модель Smi, Double, HeapObject и Tagged
MapUpdater в исходниках V8, генерализация полей и обновление зависимого кода
V8 internals: hidden classes & inline caches, подробный разбор Mathias Bynens
👍2🔥2