Деградация производительности Electron: почему софт становится тяжелее
Пустое приложение на Electron занимает на диске от 100 до 180 МБ. Минимальная программа в фоне может использовать 80–250 МБ оперативной памяти.

Для одного окна это уже заметный архитектурный расход; при нескольких одновременно запущенных Electron-приложениях нагрузка складывается.
Деградация производительности Electron-приложений возникает из сочетания факторов. Каждый продукт поставляет собственные Chromium и Node.js, интерфейс работает в нескольких процессах, а тяжелые модули могут загружаться при старте независимо от того, нужны ли они пользователю. Сам движок задаёт базовую стоимость, но конечное потребление зависит и от реализации: состава бандла, жизненного цикла окон, фоновых задач и управления памятью.
Изоляция Chromium и Node.js: отдельная среда для каждого приложения
Electron позволяет создавать настольные программы с веб-технологиями. У приложения есть основной процесс на Node.js и renderer-процессы, которые формируют окна и представления с помощью Chromium. Такая архитектура упрощает перенос интерфейса между платформами и разделяет задачи. За эту унификацию приходится платить ресурсами.
Каждое Electron-приложение поставляет собственные бинарные файлы Chromium и Node.js. ОС не может разделить общую память этих сред между независимыми программами так, как пользователь мог бы ожидать от набора окон одного браузера. Если запущены редактор, мессенджер и клиент управления задачами, каждый из них несёт собственную базовую стоимость среды выполнения.
Это объясняет, почему небольшая функциональность не гарантирует маленький размер приложения. Даже простой экран с несколькими элементами интерфейса запускается внутри полноценного набора компонентов. В результате объём дистрибутива и базовое потребление памяти слабо связаны с количеством видимых кнопок.
При этом размер установленной программы и использование RAM — разные метрики. Бинарники Chromium и Node.js влияют прежде всего на диск. Память расходуется во время работы: её занимают процессы, загруженный JavaScript, DOM, изображения, кэш и данные, необходимые конкретным функциям. Большой установщик сам по себе не доказывает утечку памяти, а высокий расход RAM не обязательно вызван только движком.
Electron задаёт минимальную стоимость среды. Архитектура и код приложения определяют, насколько далеко фактическая нагрузка от этого минимума.
Main и Renderer: цена разделения процессов
В Electron есть один Main-процесс и несколько Renderer-процессов. Main отвечает за системную часть приложения, а Renderer обслуживают окна и представления. Изоляция помогает локализовать сбои: проблема в интерфейсном процессе не обязательно приводит к падению всего приложения. Но каждый дополнительный процесс требует собственных ресурсов.
Число окон не всегда равно числу Renderer-процессов: конкретная схема зависит от того, как приложение организует представления. Однако связь между количеством активных частей интерфейса и потреблением памяти остаётся архитектурно значимой. Окна, фоновые страницы и встроенные представления могут удерживать отдельные наборы данных и выполнять работу даже тогда, когда пользователь с ними не взаимодействует.
При аудите недостаточно посмотреть на общий показатель RAM в диспетчере задач. Нужно сопоставить потребление с состоянием приложения:
- сколько процессов работает после запуска и после открытия основных разделов;
- какие окна или представления остаются активными в фоне;
- растёт ли память после переключения между разделами;
- возвращается ли потребление к сопоставимому уровню после закрытия временного окна;
- продолжает ли Renderer выполнять задачи, когда интерфейс скрыт.
Такой срез отделяет ожидаемую стоимость многопроцессной модели от дефекта управления жизненным циклом. Если память увеличивается при открытии нового функционального блока, это может быть нормальным. Если она продолжает расти при повторении одного и того же действия и не стабилизируется, следует искать удерживаемые объекты, повторные подписки, кэш без ограничений или фоновые задачи.
Сравнение с нативным приложением требует осторожности. Нативный продукт может использовать системные библиотеки и другой стек интерфейса, поэтому его базовая стоимость часто устроена иначе. Но прямой вывод по одному числу RAM будет некорректным: приложения должны выполнять сопоставимую работу, а измерение должно учитывать одинаковые сценарии, число открытых окон и состояние кэша.
| Участок сравнения | Electron-приложение | Нативное приложение |
|---|---|---|
| Среда выполнения | Обычно поставляются Chromium и Node.js | Зависит от платформы и выбранного стека |
| Изоляция интерфейса | Main и Renderer-процессы; возможны несколько Renderer | Модель процессов зависит от реализации |
| Переносимость интерфейса | Общая веб-основа для разных платформ | Часто требуется отдельная платформенная адаптация |
| Интерпретация RAM | Нужно учитывать процессы, DOM, JavaScript и кэш | Нужно учитывать собственные компоненты и модель памяти |
Таблица описывает типичные архитектурные различия, а не гарантирует преимущество одной технологии во всех задачах. Оптимизированное Electron-приложение может работать приемлемо, а плохо спроектированное нативное тоже способно расходовать ресурсы неэффективно.
Размер бандла и запуск: когда лишняя работа начинается раньше пользователя
Сборка приложения определяет, какой код и какие ресурсы попадают в дистрибутив. Если в неё включены неиспользуемые библиотеки, дублирующиеся зависимости или тяжёлые модули, продукт становится крупнее. Но бандл влияет не только на диск. Код, который без необходимости загружается и выполняется на старте, увеличивает время запуска и давление на память.
Одна из известных причин медленного старта — ранний вызов тяжёлых модулей через require(). Если модуль загружается при инициализации, приложение тратит время на его обработку до того, как пользователь добрался до функции, которой этот модуль нужен. Отложенная загрузка переносит эту работу на момент обращения к соответствующей функции.
Практический разбор начинается с последовательности запуска:
1. Зафиксировать время от запуска процесса до готовности основного окна и отдельно отметить момент, когда интерфейс становится отзывчивым.
2. Составить перечень модулей, загружаемых при старте, и определить, какие из них нужны для первого экрана.
3. Перенести необязательные зависимости к месту использования, если это не нарушает логику работы.
4. Повторить измерение на одинаковой конфигурации после изменения сборки.
5. Проверить, что ускорение старта не привело к задержке критической функции при первом обращении.
Само использование require() не является дефектом. Значение имеют момент вызова, объём загружаемого кода и то, выполняется ли работа синхронно в критическом пути. Точечная оптимизация старта не заменяет разбор Renderer-процессов и фоновых задач, но позволяет убрать работу, не относящуюся к первому экрану.
Сокращать бандл также нужно по результатам анализа зависимостей. Удаление пакета, который используется в редком, но критичном сценарии, ухудшит продукт. Цель — обнаружить дублирование и код, который не нужен конкретной сборке, а не добиваться минимального размера любой ценой.
V8: лимит кучи не равен лимиту всего приложения
В Electron 14, выпущенном в 2021 году, появилась Pointer Compression. В Electron 21, в 2022 году, были включены V8 Memory Cage и Sandboxed Pointers. При включённом сжатии указателей размер кучи V8 ограничен 4 ГБ.
Эту цифру часто трактуют слишком широко. Лимит относится к куче V8, а не ко всему потреблению приложения и не ко всей оперативной памяти компьютера. Он не означает, что Electron-программа всегда использует 4 ГБ, и не гарантирует, что приложение останется в пределах этого значения по сумме всех процессов. У Chromium есть собственные компоненты и структуры памяти; отдельно существуют Main и Renderer, графические ресурсы и другие системные затраты.
Для диагностики нужно разделять как минимум три вопроса:
- растёт ли heap JavaScript в конкретном процессе;
- увеличивается ли общая память процесса или группы процессов;
- соответствует ли рост пользовательским действиям и объёму открытых данных.
Если растёт heap, проверяют удерживаемые объекты JavaScript, замыкания, коллекции и обработчики событий. Если heap стабилен, а общий расход увеличивается, область поиска расширяется на изображения, кэш, Chromium-компоненты и дополнительные процессы. Если память повышается при открытии большого набора данных, это может быть ожидаемой нагрузкой, но приложению всё равно нужны ограничения и предсказуемое освобождение ресурсов.
Для сценариев, где пользователь работает с большими документами, медиа или каталогами, полезно оценивать потребление на длинной сессии. Один снимок сразу после запуска показывает только начальное состояние. Нужны повторяемые действия: открыть раздел, загрузить данные, закрыть его, вернуться к первому экрану и сравнить показатели. Так выявляется накопительный рост, который не виден в коротком smoke test.
Это относится и к рабочим процессам с изображениями. Например, приложение для семейного архива может показывать подборки снимков и создавать превью. Если интерфейс одновременно держит в памяти исходники, крупные превью и несколько открытых представлений, расход будет определяться выбранной моделью загрузки, а не только Electron. Идеи о том, как организовать сохранение фотографий детского творчества, относятся уже к содержанию архива; с технической стороны здесь важны размер изображений, кэш превью и освобождение данных после закрытия галереи. Для редакционной стороны такого архива пригодится материал о том, как фото помогает сохранить память о развитии ребёнка.
Оптимизация: сначала измерение, затем изменение архитектуры
Универсального показателя «нормального» потребления Electron нет. Базовый диапазон в 80–250 МБ относится к минимальному приложению и не описывает тяжёлый редактор, офисный пакет или клиент с несколькими сложными окнами. Также нельзя сводить все проблемы производительности десктопного софта к Chromium. На результат влияют код приложения, сборка, набор функций и режим использования.
Рациональная последовательность оптимизации выглядит так:
- Зафиксировать базовую линию. Измерить время запуска, количество процессов и потребление памяти после загрузки основного окна.
- Повторить типовой сценарий. Добавить действия, которые активируют ключевые разделы, открывают окна и загружают данные.
- Проверить жизненный цикл. Убедиться, что закрытые или скрытые представления не продолжают удерживать ненужные данные и выполнять фоновые задачи.
- Разобрать стартовую загрузку. Найти тяжёлые модули, которые подключаются до необходимости, и оценить возможность deferring.
- Проверить динамику, а не только пик. Сопоставить расход до и после операции, а затем после закрытия окна или завершения сценария.
- Сравнить сборки. Менять по одному существенному фактору и повторять измерения в одинаковых условиях.
Метрики следует привязать к пользовательским операциям и требованиям продукта. Для одного приложения критично время до интерактивности; для другого — устойчивость длинной сессии или быстрый возврат к ранее открытому документу. SLA имеет смысл задавать на измеримый сценарий, а не на абстрактное обещание низкого потребления. В CI/CD можно добавить проверки времени запуска и базового расхода памяти, если тестовая среда воспроизводима и результаты сопоставимы между сборками.
Архитектурное решение зависит от профиля нагрузки. Если продукту нужны веб-интерфейс и кроссплатформенная разработка, Electron может оставаться оправданным при контроле процессов, зависимостей и загрузки модулей. Если базовая стоимость среды конфликтует с ограничениями целевых устройств, следует сравнить альтернативный стек на одинаковом функциональном сценарии. Цена миграции, поддержка платформ и скорость разработки тоже входят в расчёт; одного замера RAM для решения недостаточно.
Для архитектурного комитета критерий простой: каждое потребление ресурсов должно быть объяснимо функцией или выбранной моделью изоляции. Рост, который нельзя связать с действием пользователя, требует профилирования. Повторяемая деградация после одинаковых операций указывает на проблему в коде или жизненном цикле. Большой базовый расход при стабильной работе может быть свойством выбранной архитектуры, но его всё равно нужно сопоставить с требованиями устройств и сценариев.
Оптимальный стек определяется не названием технологии. Решение принимают после измерений на целевом оборудовании, с типовым набором данных и реальной конфигурацией окон. Для Electron это означает отдельно учитывать цену Chromium и Node.js, мультипроцессность, загрузку модулей и поведение V8. Если эти составляющие видны в метриках, дальнейший выбор между оптимизацией текущей реализации и сменой платформы становится предметным.