Архитектура выполнения — итоги
Кратко — что стоит унести из раздела "Архитектура выполнения". Если пункт кажется туманным — откройте указанную главу или оглавление.
FAQ — Часто задаваемые вопросы
Типичные сбои при работе с производительностью, памятью, ошибками и сборкой, плюс формулировки, как их ищут в Google — с кратким ответом и ссылкой на главу. Определения для зачёта — в чек-листе.
Вопрос. Приложение "тормозит", а загрузка CPU в диспетчере задач низкая.
Ответ. Узкое место часто в I/O, блокировках или ожидании сети, а не в вычислениях. Смотрите latency запросов, очереди и время ожидания lock, а не только % CPU. Подробнее здесь — метрики, архитектура.
Вопрос. Память процесса растёт часами, хотя "GC же есть".
Ответ. Объекты удерживаются ссылками — статические коллекции, кэш без лимита, подписки на события. Снимите heap dump и найдите, кто держит корень. Подробнее здесь — архитектура, память.
Вопрос. Профилировщик показал горячую функцию, которую я не вызываю явно.
Ответ. Это может быть runtime, сериализация или скрытый LINQ/ORM. Раскройте стек вызовов в profiler до вашего кода; оптимизируйте частый путь, а не одну строку. Подробнее здесь — метрики, сборка.
Вопрос. Переписал "медленный" участок — стало ещё медленнее.
Ответ. Преждевременная или неверная оптимизация — микро-улучшение в холодном коде, регресс из-за аллокаций. Измеряйте до/после на репрезентативных данных. Подробнее здесь — сборка, архитектура.
Вопрос. Исключение поймали пустым catch — в prod "ничего не произошло", данные пропали.
Ответ. Глотание ошибок скрывает сбой. Логируйте с контекстом, rethrow или возвращайте Result; на границах сервиса — централизованный handler. Подробнее здесь — ошибки.
Вопрос. Stack trace указывает не на ту строку, где я правил код.
Ответ. Release без PDB, inlining и оптимизации сдвигают строки. Соберите с символами, используйте Source Link; для prod храните символы отдельно. Подробнее здесь — ошибки, отладка.
Вопрос. Тест 0.1 + 0.2 === 0.3 падает — программа "неправильно считает"?
Ответ. Float хранится в IEEE 754 с погрешностью; для денег используйте decimal/fixed-point или целые копейки. Сравнивайте с epsilon или типами exact arithmetic. Подробнее здесь — битовые операции.
Вопрос. Сдвиг битов вправо дал отрицательное число — я же делил на 2.
Ответ. Для signed типов >> может быть арифметическим с сохранением знака. Используйте unsigned или явное деление, если нужна другая семантика. Подробнее здесь — битовые операции.
Вопрос. Оставил "мёртвый" код "на всякий случай" — коллеги боятся удалять.
Ответ. Неиспользуемый код — технический долг: путает читателя и ломает рефакторинг. Удаляйте с тестами; история в Git. Подробнее здесь — мёртвый код.
Вопрос. Логирую каждый запрос в DEBUG — в prod забыли выключить, диск забился.
Ответ. Уровни логирования и structured logging с ротацией; verbose только по флагу. Лог не заменяет метрики для SLA. Подробнее здесь — ошибки, метрики.
Вопрос. В prod нет логов — не понимаю, почему упало у пользователя.
Ответ. Добавьте correlation id, уровни Error/Warn и централизованный сбор (ELK, Loki). Логируйте необработанные исключения на верхнем уровне. Подробнее здесь — ошибки.
Вопрос. Grafana "зелёная", пользователи жалуются на лаги.
Ответ. Среднее CPU скрывает хвост latency (p95/p99). Добавьте histogram задержек и трассировку запросов, а не только gauge CPU. Подробнее здесь — метрики.
Вопрос. Рекурсия работает на 100 элементах, на 100 000 — stack overflow только на сервере.
Ответ. Лимит стека на сервере меньше или данные больше. Перепишите на итерацию или tail-recursion там, где язык поддерживает; увеличение stack — временная мера. Подробнее здесь — вызовы, функции.
Вопрос. Внедрил object pool — код сложнее, а выигрыша не видно.
Ответ. Pool оправдан при частых аллокациях однотипных объектов в hot path. Сначала профилируйте GC/allocation rate; иначе pool — лишняя сложность. Подробнее здесь — архитектура.
Вопрос. Подписался на событие и забыл отписаться — память растёт при каждом открытии экрана.
Ответ. Классическая утечка через event handler. Отписывайтесь в dispose/onDestroy или используйте weak event pattern. Подробнее здесь — архитектура.
Вопрос. finally перезаписал исключение из try — в логе не та ошибка.
Ответ. Новое исключение в finally маскирует исходное. Не throw из finally без крайней нужды; в C# используйте exception filter и логирование обоих. Подробнее здесь — ошибки.
Вопрос. Ранний return пропустил закрытие файла — "too many open files".
Ответ. Используйте try-with-resources / using / defer, чтобы cleanup выполнялся при любом выходе. Подробнее здесь — ошибки, ресурсы.
Вопрос. Оптимизировал алгоритм, но диск стал на 100% — что не так?
Ответ. Новый алгоритм мог увеличить random I/O или swap. Смотрите iowait, размер working set, индексы БД. Подробнее здесь — метрики.
Вопрос. Команда меряет только "среднее время ответа" — SLA всё равно нарушается.
Ответ. Добавьте percentiles и SLO по хвосту; один медленный запрос из ста бьёт по репутации. Подробнее здесь — метрики.
Вопрос. Собираю Release без флагов — "и так быстро".
Ответ. Убедитесь, что оптимизации и LTO/PGO включены там, где нужно; Debug на prod недопустим. Документируйте профиль сборки в CI. Подробнее здесь — сборка, сборка приложений.
Вопрос. Коллеги говорят "не трогай, работает" — perf-регрессии копятся.
Ответ. Введите budget на latency и регрессионные бенчмарки в CI хотя бы для критичных endpoint. Культура — измерять, а не спорить. Подробнее здесь — сборка.
Вопрос. После смены структуры данных cache miss вырос — CPU вроде свободен, но медленно.
Ответ. Память перестала быть cache-friendly — linked list vs array, false sharing. Профилируйте cache misses (perf, VTune). Подробнее здесь — архитектура, процессор.
Вопрос. Исключение в финализаторе/destructor — процесс падает "ниоткуда".
Ответ. Finalize не для бизнес-логики; порядок вызова не детерминирован. Освобождайте ресурсы явно в dispose/using. Подробнее здесь — ошибки.
Вопрос. Удалил "unused" метод — сломался плагин через reflection.
Ответ. Статический анализ не видит динамические вызовы. Ищите [UsedImplicitly], contract tests, grep по имени в конфигах перед удалением. Подробнее здесь — мёртвый код.
Вопрос. Отладчик показывает странные значения переменных — как доверять состоянию?
Ответ. Оптимизатор мог переупорядочить код; смотрите memory/watch с отключённой оптимизацией или логируйте snapshot. Подробнее здесь — отладка.
Вопрос. Виртуальная память "кончилась" — swap на диск, всё ползёт.
Ответ. Working set превысил RAM — thrashing. Уменьшите footprint, добавьте RAM, ограничьте параллелизм тяжёлых задач. Подробнее здесь — память, метрики.
Вопрос. Не понимаю, когда бросать exception, а когда возвращать код ошибки.
Ответ. Exception — для нештатных ситуаций, которые прерывают нормальный поток; коды/Result — для ожидаемых веток (404, validation). Не используйте exception для control flow в hot path. Подробнее здесь — ошибки.
Вопрос. Как оптимизировать производительность программы?
Ответ. Сначала профилируйте, найдите 10% кода с 90% времени, оптимизируйте узкое место; не гадайте. Подробнее здесь — метрики, сборка.
Вопрос. Что такое bottleneck (узкое место) в системе?
Ответ. Компонент, ограничивающий скорость всей цепочки — медленный диск, один lock, N+1 запросов к БД. Подробнее здесь — метрики.
Вопрос. Что такое profiling (профилирование кода)?
Ответ. Замер где тратится CPU и память — flame graph, dotTrace, perf. Отличается от простого console.time на одной функции. Подробнее здесь — метрики, отладка.
Вопрос. Что такое memory leak (утечка памяти)?
Ответ. Память не возвращается — забытые ссылки, event handlers, нативные буферы; возможна даже с GC. Подробнее здесь — архитектура, память.
Вопрос. Как работает garbage collector (GC)?
Ответ. Помечает объекты достижимые от корней, остальное освобождает; поколения (gen0/1/2) ускоряют сбор молодых объектов. Подробнее здесь — архитектура, сборка мусора.
Вопрос. Что такое exception (исключение) в программе?
Ответ. Механизм сигнала ошибки с раскруткой стека до обработчика try/catch. Подробнее здесь — ошибки.
Вопрос. Что такое stack trace?
Ответ. Цепочка вызовов в момент ошибки — файл, строка, метод; читайте до первого своего кадра. Подробнее здесь — ошибки, вызовы.
Вопрос. Что такое преждевременная оптимизация?
Ответ. Ускорение кода до измерений — усложняет поддержку без выигрыша. Сначала корректность, потом профиль. Подробнее здесь — сборка.
Вопрос. Правило 90/10 в оптимизации — что это?
Ответ. Около 90% времени в 10% кода — оптимизируйте этот hot path, а не всё подряд. Подробнее здесь — сборка, метрики.
Вопрос. Latency и throughput — в чём разница?
Ответ. Latency — время одного запроса; throughput — сколько запросов в секунду. Низкая latency при высоком throughput — цель SLA. Подробнее здесь — метрики.
Вопрос. Почему 0.1 + 0.2 не равно 0.3 в JavaScript?
Ответ. Числа float в IEEE 754 — двоичное представление 0.1 периодическое; для денег используйте decimal. Подробнее здесь — битовые операции.
Вопрос. Что такое битовые операции AND OR XOR?
Ответ. Операции над отдельными битами — маски, флаги, быстрая арифметика, crypto. Подробнее здесь — битовые операции.
Вопрос. Что такое IEEE 754?
Ответ. Стандарт чисел с плавающей точкой — sign, exponent, mantissa; объясняет погрешности float. Подробнее здесь — битовые операции.
Вопрос. Что такое технический долг (technical debt)?
Ответ. Накопленные упрощения и мёртвый код, замедляющие изменения; платят процентами времени на фичи. Подробнее здесь — мёртвый код.
Вопрос. Что такое dead code (мёртвый код)?
Ответ. Недостижимые или никогда не вызываемые участки — шум в репозитории; удаляйте с тестами. Подробнее здесь — мёртвый код.
Вопрос. Что такое object pool (пул объектов)?
Ответ. Переиспользование заранее созданных объектов вместо частых new — снижает давление на GC в hot path. Подробнее здесь — архитектура.
Вопрос. Что такое cache-friendly код?
Ответ. Данные лежат плотно и последовательно — array лучше linked list для CPU cache. Подробнее здесь — архитектура, процессор.
Вопрос. Уровни логирования DEBUG INFO WARN ERROR — зачем?
Ответ. Фильтрация шума — в prod INFO и выше, DEBUG только при расследовании. Подробнее здесь — ошибки.
Вопрос. Что такое p95 и p99 latency?
Ответ. 95% и 99% запросов быстрее этого времени — хвост важнее среднего для UX. Подробнее здесь — метрики.
Вопрос. Release vs Debug сборка — в чём разница?
Ответ. Debug — символы и без оптимизаций для отладки; Release — быстрее, меньше бинарник, сложнее step-through. Подробнее здесь — сборка, сборка приложений.
Вопрос. Что такое stack unwinding при exception?
Ответ. Раскрутка стека — вызов destructors и поиск catch при пробросе исключения вверх. Подробнее здесь — ошибки.
Вопрос. Как ускорить Java или Python приложение?
Ответ. Профиль JVM/asyncio, меньше аллокаций, правильные структуры данных и I/O; смена языка — последний шаг. Подробнее здесь — архитектура, оптимизация.
Что запомнить
Архитектура выполнения программ определяет, как код превращается в поведение системы. Она охватывает всё — от низкоуровневого представления данных в памяти до высокоуровневых абстракций вроде потоков, исключений и асинхронных вызовов. Понимание этой архитектуры позволяет не просто писать работающий код, а создавать программы, которые эффективны, предсказуемы и устойчивы под нагрузкой.
Центральную роль играют процессы и потоки. Процесс изолирует программу в собственном адресном пространстве, обеспечивая стабильность и безопасность. Потоки внутри процесса позволяют выполнять задачи конкурентно, разделяя память, но требуя тщательного управления доступом к общим данным. Механизмы синхронизации — мьютексы, семафоры, атомарные операции — защищают от гонок данных, но при неправильном использовании могут привести к взаимоблокировкам или голоданию.
Производительность — это не только скорость, но и рациональное использование ресурсов — CPU, памяти, диска и сети. Ключевой принцип — оптимизировать не весь код, а только узкие места, выявленные профилированием. Правило 90/10 гласит, что 90% времени выполнения приходится на 10% кода, и именно на них стоит сосредоточиться. Преждевременная оптимизация, напротив, усложняет код без реальной пользы.
Современные языки скрывают сложность управления памятью за сборщиками мусора, но это не освобождает от ответственности. Утечки памяти возможны даже при автоматической сборке, если объекты удерживаются ссылками. Эффективное управление памятью включает минимизацию аллокаций в горячих путях, использование пулов объектов и понимание поколенческой модели GC.
Обработка ошибок — неотъемлемая часть надёжной архитектуры. Исключения позволяют отделять основную логику от обработки сбоев, но их нельзя игнорировать. Неуправляемые исключения приводят к аварийному завершению, поэтому на границах компонентов и на верхнем уровне приложения всегда должна быть обработка ошибок с логированием.
Асинхронность решает проблему блокирующих операций ввода-вывода, позволяя одной системе обслуживать тысячи соединений. Модели вроде Event Loop, корутин и async/await делают асинхронный код читаемым и поддерживаемым. Современные протоколы — WebSocket, SSE, gRPC — обеспечивают эффективный обмен данными в реальном времени.
В конечном счёте, архитектура выполнения — это баланс между абстракцией и контролем. Чем выше уровень абстракции, тем проще писать код, но тем меньше контроля над ресурсами. Разработчик должен уметь спускаться на более низкие уровни, когда возникают проблемы с производительностью, стабильностью или корректностью поведения.
Куда идти дальше
| Тема | Раздел |
|---|---|
| "Асинхронность — о разделе" | "Асинхронность — о разделе" |
| "Парадигмы и уровни абстракции — о разделе" | "Парадигмы и уровни абстракции — о разделе" |
| "Проект, структура и фреймворки — о разделе" | "Проект, структура и фреймворки — о разделе" |
| "Объектно-ориентированное программирование — о разделе" | "Объектно-ориентированное программирование — о разделе" |