# Сначала измерь, потом ускоряй

Экспресс-курс о производительности. 20 понятий — Profiling, Latency, Throughput, Batching, Bottleneck, Amdahl's Law, Big-O, N+1 Query, Caching, Percentiles, Tail Latency, Premature Optimization и другие — у каждого английское название, схема и карточка «что это → как работает → пример → подвох». В конце — шпаргалка. Главная мысль: интуиция о медленности обычно неверна, так что профилируй, чини то одно, что доминирует, и измеряй снова.

- **Author:** Фёдор Чищин
- **Level:** intermediate
- **Reading time:** ~35 минут чтения
- **Published:** 2026-06-13
- **Source:** https://fedorthinks.com/ru/learn/performance-express

---

## 1. Измеряй, не гадай

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

### Интуиция о медленности обычно неверна

> Врач, который назначает операцию по догадке, ни разу не сделав анализ, — уверенный, быстрый и слишком часто оперирующий совсем не то.

Разработчики известны тем, что плохо угадывают, куда уходит время. Часть, в медленности которой ты уверен, часто в порядке. Настоящий виновник сидит там, где ты не ждал: крошечная функция, вызванная миллион раз, или скрытый вызов базы.

Действовать по чутью — значит оптимизировать не то, добавить сложность и остаться с медленной программой. Первый ход — никогда не «почини», а **узнай, куда на деле уходит время**.

### Карта: 20 понятий в семи группах

> Карта метро: сначала видишь линии целиком, потом едешь по станциям.

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

_Diagram: Семь групп понятий — по ним идут главы курса._

### Как читать курс

> Механик не меняет детали наугад: сначала приборы, потом одна починка, потом проверка.

У каждого понятия одно и то же: **английское название** (запомни его — это термин из документации, профайлеров и дашбордов), образ из жизни, **схема** и **карточка** с полями _что это_, _как работает_, _пример_, _подвох_ и _связи_.

Главная идея курса: производительность — это не набор трюков, а метод. **Измерь → найди узкое место → почини одно → измерь снова → остановись на цели.** Всё остальное в курсе — детали этого цикла.

### Profiling — найди реальную стоимость

> Детализированный счёт, где видно, куда ушли деньги, строка за строкой. Ты перестаёшь гадать и видишь, что бюджет съел один платёж — и не тот, на который ты думал.

**Профайлер (profiler)** — инструмент, который меряет, где программа на деле проводит время, функция за функцией. Он превращает «думаю, это медленно» в «эта одна функция — 80% времени выполнения». С этим ты чинишь то, что важно, а не то, что вообразил.

Профайлер ли это, логи с таймингами или метрики наблюдаемости (observability) — принцип один: **пусть на проблему указывает измерение, а не интуиция**.

_Diagram: Профиль показывает долю времени по функциям. Ты подозревал рендер (8%), а 80% съедает один запрос к базе._

#### Profiling (профилирование, profiler)

- **Что это:** Измерение того, где программа на деле тратит время, по функциям.
- **Как работает:** Профайлер (или логи с таймингами, или метрики) записывает время каждой части и показывает доли.
- **Пример:** «`getOrders()` — 80% времени запроса» вместо «наверное, тормозит рендер».
- **Подвох:** Профиль на игрушечных данных может указать не туда — меряй на реалистичной нагрузке.
- **Связи:** Первый шаг **Measure–Fix–Measure**; именно он находит **Bottleneck**.

### Measure–Fix–Measure — измерь, почини, измерь снова

> Учёный меняет одну переменную, перезапускает опыт и проверяет результат — никогда не предполагая, что изменение помогло, всегда подтверждая.

Производительность — это цикл: измерь, чтобы найти медленную часть, измени её, потом **измерь снова**, чтобы подтвердить, что изменение правда помогло, — а не просто сдвинуло проблему или сделало хуже.

Без второго измерения ты гадаешь, улучшил ли что-то. Именно так «оптимизации» тихо делают всё медленнее. Относись к каждой починке как к **гипотезе**, которую проверяешь, — та же дисциплина, что эвалы для ИИ или тесты для кода.

_Diagram: Измерь → поменяй одно → измерь тем же тестом. Быстрее — оставляешь, нет — откатываешь._

#### Measure–Fix–Measure (измерь, почини, измерь снова)

- **Что это:** Каждая починка проверяется вторым измерением до и после.
- **Как работает:** Один и тот же тест до изменения и после; меняешь одну вещь за раз, чтобы знать, что сработало.
- **Пример:** Добавил индекс: запрос был 400 мс, стал 40 мс — оставляешь. Не изменилось — откатываешь.
- **Подвох:** Без второго замера «оптимизация» может тихо замедлить систему, и никто не заметит.
- **Связи:** Ядро **Optimization Loop**; родственник эвалов и тестов.

> **Вопрос рамки:** «Я правда измерил, куда уходит время, — или вот-вот оптимизирую что-то по чутью, которое, скорее всего, указывает не туда?»

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

## 2. Latency и Throughput

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

### Latency — ожидание одной вещи

> Сколько занимает один кофе от заказа до чашки — опыт одного клиента, от начала до конца.

**Задержка (latency)** — сколько занимает одна операция от начала до конца: один запрос, одна загрузка страницы, один запрос к базе. Это то, что отдельный пользователь _чувствует_: ожидание.

Когда кто-то говорит «сайт медленный», он почти всегда имеет в виду задержку — паузу между действием и ответом. Снижать задержку — значит делать каждую отдельную вещь быстрее.

_Diagram: Задержка — весь путь одного запроса: сеть, работа сервера, сеть обратно, отрисовка._

#### Latency (задержка, время ответа)

- **Что это:** Время одной операции от начала до конца, обычно в миллисекундах.
- **Как работает:** Складывается из всех шагов пути: сеть, работа сервера, база, отрисовка.
- **Пример:** От клика до готовой страницы — 800 мс.
- **Подвох:** Одна цифра задержки — обычно среднее, а оно прячет медленные случаи (глава 5).
- **Связи:** Отдельная цель от **Throughput**; меряется через **Percentiles**.

### Throughput — сколько обрабатываешь в секунду

> Сколько кофе вся кофейня подаёт за час — не сколько занимает один, а общий поток через заведение.

**Пропускная способность (throughput)** — сколько работы система обрабатывает за единицу времени: запросов в секунду, задач в минуту. Это про общую ёмкость, а не про отдельное ожидание.

У системы может быть отличная пропускная способность (обслуживает тысячи разом), пока каждый пользователь всё равно ждёт (высокая задержка). Или низкая задержка, но ограниченная ёмкость. Это разные цели, и от того, какую ты гонишь, зависит, что ты чинишь.

_Diagram: Пропускная способность — общий поток через систему: сколько запросов в секунду она успевает обработать._

#### Throughput (пропускная способность, req/s)

- **Что это:** Объём работы за единицу времени: запросов в секунду, задач в минуту.
- **Как работает:** Растёт, когда система обрабатывает больше параллельно или тратит меньше на каждую единицу.
- **Пример:** API держит 1 000 запросов в секунду; ночной конвейер — миллион строк в час.
- **Подвох:** Высокий throughput не значит, что пользователю быстро: каждый может ждать долго.
- **Связи:** Часто растёт за счёт **Latency** через **Batching**.

### Batching — пропускная способность ценой задержки

> Мотоцикл довозит одного человека быстрее всех (низкая задержка); автобус перевозит больше всего людей за рейс (высокая пропускная способность). Лучший транспорт зависит от того, что тебе нужно.

Задержка и пропускная способность независимы, и оптимизация одной может навредить другой. **Батчинг (batching)** — группировка работы — часто поднимает пропускную способность, но добавляет задержку: каждый элемент ждёт, пока соберётся пачка.

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

_Diagram: По одному — каждый элемент уходит сразу, но поездок много. Пачкой — одна поездка, больше в секунду, но каждый ждёт пачку._

#### Batching (батчинг, группировка работы)

- **Что это:** Собирать много мелких операций в одну большую.
- **Как работает:** Накладные расходы (поездка по сети, запуск задачи) платятся один раз на пачку, а не на элемент.
- **Пример:** Вставить 1 000 строк одним запросом вместо 1 000 отдельных.
- **Подвох:** Каждый элемент ждёт, пока пачка наберётся, — задержка растёт.
- **Связи:** Меняет **Latency** на **Throughput**; та же идея, что лечит **N+1 Query**.

> **Вопрос цели:** «Я хочу, чтобы каждая операция ощущалась быстрее (задержка), или чтобы система обрабатывала больше общего объёма (пропускная способность)? Это разное, и починка одного может не помочь другому.»

> Задержка — это ожидание одной операции; пропускная способность — сколько обрабатываешь в секунду. Это независимые цели — а батчинг часто меняет задержку на пропускную способность.

## 3. Найди узкое место

Время не размазано ровно. Почти всегда одна часть доминирует — и оптимизировать что-то ещё бессмысленно, пока не починишь её.

### Bottleneck — одна медленная часть доминирует

> Цепь ровно настолько крепка, насколько её слабейшее звено. Усиливать крепкие звенья бесполезно — только слабое решает, выдержит ли она.

В большинстве медленных систем одно **узкое место (bottleneck)** отвечает за бо́льшую часть времени — один запрос, один медленный сервис, один плохой цикл. Всё остальное уже достаточно быстро.

Поэтому гадать так расточительно: весь навык — найти **доминирующую стоимость**, а не улучшать быстрые части.

_Diagram: Запрос идёт через пять шагов, но 400 мс из 500 — один запрос к базе. Это и есть узкое место._

#### Bottleneck (узкое место)

- **Что это:** Та одна часть, которая занимает бо́льшую часть общего времени.
- **Как работает:** Профиль показывает доли; обычно одна часть резко больше остальных.
- **Пример:** Страница грузится 500 мс, и 400 из них — один запрос к базе без индекса.
- **Подвох:** Узкое место редко там, где ты его ждёшь, — поэтому сначала профиль.
- **Связи:** Находится через **Profiling**; почему его чинить — объясняет **Amdahl's Law**.

### Limiting Step — система быстра настолько, насколько её самый медленный шаг

> Шоссе свободно, пока не одно перекрытие полосы, где все ползут. Скорость всей поездки задаёт это одно место, а не свободные участки.

Запрос, который проходит десять шагов, ограничен самым медленным из них — это **ограничивающий шаг (limiting step)**. Ускорить девять быстрых шагов — итог почти не сдвинется; темп задаёт медленный. Так что ты целишься именно в него.

И заметь: починка одного узкого места часто _открывает_ следующее. Убери самый медленный шаг — появится новый самый медленный. Работа над производительностью — это повторяющийся поиск и расчистка того, что ограничивает систему _сейчас_.

_Diagram: Починили запрос: 500 → 140 мс. Теперь самый медленный шаг — рендер, и следующий раунд целится в него._

#### Limiting Step (ограничивающий шаг, следующее узкое место)

- **Что это:** Шаг, который сейчас задаёт скорость всего пути.
- **Как работает:** Убрал один ограничивающий шаг — ограничивающим становится следующий по медленности.
- **Пример:** Ускорили запрос в 10 раз — теперь рендер в 50 мс стал самым медленным шагом.
- **Подвох:** Узкое место не исчезает навсегда, оно переезжает — поэтому нужна цель, где остановиться.
- **Связи:** Причина, почему **Optimization Loop** — цикл, а **Performance Target** — его выход.

### Amdahl's Law — оптимизация в другом месте впустую

> Полировать кубок, пока у машины спущено колесо, — усилие там, где оно ничего не меняет, а то, что тебя на деле останавливает, лежит нетронутым.

Оптимизируешь часть, которая занимает 2% времени, — лучшее, что выиграешь, это 2%. Найди часть на 80% — и её починка меняет всё. Эту арифметику называют **законом Амдала (Amdahl's law)**: выигрыш ограничен долей времени, которую занимает ускоряемая часть.

Время на оптимизацию чего угодно, кроме узкого места, по определению не может заметно помочь. Часто оно делает хуже — сложность и баги ради невидимого выигрыша. Измерение не даёт тебе любовно полировать часть, которую ты понимаешь, и заставляет взяться за ту, что правда стоит. **Чини узкое место, остальное игнорируй** — пока остальное само не станет узким местом.

_Diagram: Ускорить рендер вдвое — минус 5%. Ускорить запрос вдвое — минус 40%. Выигрыш ограничен долей части._

#### Amdahl's Law (закон Амдала)

- **Что это:** Ускорение части системы ограничено тем, какую долю времени эта часть занимает.
- **Как работает:** Часть на 10% даже при бесконечном ускорении сэкономит максимум 10%.
- **Пример:** Рендер (10%) вдвое быстрее: 500 → 475 мс. Запрос (80%) вдвое быстрее: 500 → 300 мс.
- **Подвох:** Приятно оптимизировать то, что понимаешь, — но если это не узкое место, выигрыша не будет.
- **Связи:** Математика за **Bottleneck**; аргумент против **Premature Optimization**.

> **Вопрос узкого места:** «Какая одна часть доминирует во времени здесь — и чиню ли я её или полирую ту, что уже достаточно быстра, чтобы почти не иметь значения?»

> Одно узкое место обычно доминирует во времени, и система быстра ровно настолько, насколько её самый медленный шаг. Чини его; оптимизация чего угодно ещё впустую, пока оно не станет узким местом.

## 4. Крупнейшие выигрыши

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

### Better Algorithm (Big-O) — лучший алгоритм бьёт более быструю машину

> Два маршрута в один город: быстрая машина на длинной извилистой дороге всё равно проигрывает медленной на прямом срезе. Маршрут важнее двигателя.

Крупнейшие выигрыши часто дают лучший **алгоритм** или структура данных, а не железо и не микроправки. Как растёт время с ростом входа, описывает **Big-O**. Цикл **O(n²)**, заменённый на поиск **O(n)** через хеш-таблицу, может превратить минуты в миллисекунды — никакая более быстрая машина так не сможет.

Прежде чем оптимизировать мелочь, спроси, не неверен ли сам _подход_. Лучшая форма, как показывает курс о структурах данных, — самое дешёвое и самое крупное ускорение из всех.

_Diagram: O(n²) взлетает с ростом входа, O(n) растёт ровно. На больших данных это разница между минутами и миллисекундами._

#### Better Algorithm (Big-O, алгоритмическая сложность)

- **Что это:** Сменить подход так, чтобы работа росла медленнее с ростом данных.
- **Как работает:** Big-O показывает рост: O(n²) — вложенный цикл «каждый с каждым», O(n) — один проход.
- **Пример:** Найти общие элементы двух списков: вложенный цикл → множество (hash set) и один проход.
- **Подвох:** На маленьких данных разницы не видно — она взрывается, когда данных становится много.
- **Связи:** Главный выбор **Performance by Design**; см. курс о структурах данных.

### N+1 Query — слишком много круговых поездок

> Двадцать отдельных походов в магазин за одной вещью каждый вместо одного похода со списком — твой день съедает дорога, а не покупки.

Огромная доля медленности — **слишком много круговых поездок (round trips)**: повторные вызовы базы или сервиса, каждый дешёвый сам по себе, но разрушительный в массе.

Классика — **запрос N+1 (N+1 query)**: достать список, а потом сделать ещё по запросу на каждый элемент. Один поход превращается в сотни. Лекарство — **доставать оптом**, спросить всё разом. Уменьшить _число_ поездок обычно выгоднее, чем ускорять каждую, потому что сама поездка и есть стоимость.

_Diagram: Слева: 1 запрос за список + N запросов по одному. Справа: один запрос за всё сразу._

#### N+1 Query (проблема N+1, лишние round trips)

- **Что это:** Один запрос за список, потом по запросу на каждый элемент.
- **Как работает:** Каждая поездка в базу стоит сети и накладных расходов; N поездок умножают эту цену на N.
- **Пример:** 100 заказов → 1 запрос за список + 100 запросов за клиентов. Починка: один запрос с `IN (…)` или JOIN.
- **Подвох:** ORM прячет это: код выглядит как простой цикл, а под ним — сотни запросов.
- **Связи:** Лечится той же идеей, что **Batching**; частый **Bottleneck**.

### Caching — не считай одно и то же дважды

> Готовить блюдо с нуля каждый раз, когда кто-то просит, вместо того чтобы держать кастрюлю тёплой, — самая быстрая работа та, что не повторяешь.

**Кэширование (caching)** — запомнить дорогой результат, чтобы не пересчитывать его. Это один из крупнейших рычагов вообще (у него свой курс).

Первый запрос идёт по долгому пути и сохраняет результат; следующие получают его из памяти. Цена — память и риск отдать устаревшие данные.

_Diagram: Попадание (hit) — ответ из памяти. Промах (miss) — считаем по-долгому и запоминаем результат на следующий раз._

#### Caching (кэширование)

- **Что это:** Хранить результат дорогой работы и отдавать его повторно.
- **Как работает:** Проверяем кэш: есть — отдаём сразу (hit); нет — считаем, сохраняем, отдаём (miss).
- **Пример:** Страница каталога собирается 300 мс; из кэша — за пару миллисекунд.
- **Подвох:** Кэш может отдать устаревшие данные; когда его сбрасывать — отдельная трудная задача.
- **Связи:** Частный случай **Doing Less**; подробно — в курсе о кэшировании.

### Doing Less — самая быстрая работа та, что ты пропустил

> Не готовить то, что никто не заказал, и не накрывать стол на двадцать, когда пришли трое.

Ещё одна классика — **делать меньше (doing less)**. Вычисляй вещи **лениво (lazy)** — только когда они правда нужны. Не загружай данные, которые не используешь. Пропускай работу, результат которой всё равно выбросишь.

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

_Diagram: Из всей работы реально нужна часть. Остальное — лишние данные, преждевременные вычисления и выброшенные результаты._

#### Doing Less (lazy evaluation, ленивые вычисления)

- **Что это:** Убрать работу, а не ускорять её.
- **Как работает:** Ленивые вычисления, загрузка только нужных полей, отказ от результатов, которые не нужны.
- **Пример:** Тянуть из базы 3 нужных поля, а не все 50; считать отчёт, только когда его открыли.
- **Подвох:** Ленивая работа может выстрелить позже, в неудобный момент, — меряй.
- **Связи:** Включает **Caching** (не повторять) и лечит часть **N+1 Query**.

> **Вопрос выигрышей:** «Узкое место — это плохой алгоритм, слишком много круговых поездок, нет кэша или работа, которую можно пропустить, — та горстка починок, что стоит за большинством реальных ускорений?»

> Большинство ускорений — несколько классик: лучший алгоритм, меньше круговых поездок (убей N+1), кэширование дорогих результатов и просто меньше работы. Самая быстрая работа — та, что ты пропустил.

## 5. Среднее и хвост

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

### Average — среднее прячет медленные случаи

> В комнате девять человек в комфорте и один в огне — средняя температура приятная. Среднее стирает тот случай, который на деле важен.

Сообщать производительность как **среднее (average, mean)** опасно успокаивает: оно смешивает быстрое большинство с медленным меньшинством в одно счастливое число. Но пользователи не живут в среднем — каждый живёт в _своём_ запросе, и медленные чувствуют каждую миллисекунду.

Среднее время ответа 200 мс может прятать, что один пользователь из двадцати ждёт пять секунд. Среднее говорит «всё в порядке», пока реальная доля пользователей страдает.

_Diagram: Девятнадцать быстрых запросов и один на 5 секунд — а линия среднего всё равно выглядит спокойно._

#### Average (Mean) (среднее, ловушка среднего)

- **Что это:** Сумма времён, делённая на число запросов.
- **Как работает:** Смешивает быстрые и медленные запросы в одно число.
- **Пример:** «Среднее — 200 мс», а каждый двадцатый ждёт 5 секунд.
- **Подвох:** Выглядит прилично, пока часть пользователей страдает. Нельзя ставить на нём цели.
- **Связи:** Заменяется на **Percentiles**; прячет **Tail Latency**.

### Percentiles — p50 и p99 показывают реальный опыт

> Отчёт, где написано не только типичное ожидание, но и «самый медленный 1% людей ждал вот столько», — плохие случаи названы, а не усреднены.

**Перцентили (percentiles)** показывают то, что прячет среднее. **p50** (медиана) — типичный случай: половина запросов быстрее. **p99** — медленный хвост: 99% быстрее, так что это примерно худший опыт, который встречает реальный пользователь.

Следить за p99, а не только за средним, — вот как ты видишь тех, кто правда страдает. Хвост — это место, где живёт боль, а перцентили делают её видимой.

_Diagram: Распределение времени ответа: p50 — где-то в толпе быстрых, p99 — далеко в медленном хвосте._

#### Percentiles (перцентили, p50 / p95 / p99)

- **Что это:** pN — время, быстрее которого N% запросов. p50 — медиана, p99 — хвост.
- **Как работает:** Сортируешь времена ответа и берёшь значение на нужной позиции.
- **Пример:** p50 = 120 мс (типично), p99 = 2 с (каждый сотый ждёт две секунды).
- **Подвох:** Перцентили нельзя усреднять между серверами — считай их по всем запросам сразу.
- **Связи:** Делают видимой **Tail Latency**; на них ставят **Performance Target**.

### Tail Latency — то, что пользователи помнят

> Один ужасный ужин в ресторане перевешивает в памяти дюжину нормальных — худшие переживания, а не среднее, формируют то, как тебя судят.

Медленные запросы — **хвост (tail latency)** — непропорционально формируют то, как пользователи воспринимают продукт, потому что плохой опыт запоминается.

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

_Diagram: Страница ждёт самый медленный из своих вызовов. При 100 вызовах и 1% медленных хвост ловят около 63% страниц._

#### Tail Latency (хвостовая задержка)

- **Что это:** Время самых медленных запросов — p99 и дальше.
- **Как работает:** Когда один ответ собирается из многих вызовов, он ждёт самый медленный из них.
- **Пример:** Каждый вызов медленный лишь в 1% случаев, но страница из 100 вызовов попадает в хвост примерно в 63% случаев.
- **Подвох:** Чем больше вызовов в ответе, тем чаще «редкий» хвост становится нормой.
- **Связи:** Видна через **Percentiles**; прячется в **Average**.

> **Вопрос измерения:** «Я смотрю на среднее и мне спокойно — или на p99, где видны пользователи, которые правда страдают?»

> Среднее прячет медленные случаи, которые пользователи реально чувствуют. Следи за перцентилями — p50 для типичного, p99 для болезненного хвоста — потому что люди помнят худшее.

## 6. Преждевременная оптимизация

Есть ошибка, обратная игнорированию производительности: гнаться за ней слишком рано, везде, ещё не зная, что она важна. Знаменитое предупреждение об этом знаменито не зря.

### Premature Optimization — корень всех зол

> Укреплять каждую стену дома против землетрясения, которого может и не быть, до того как кто-то вообще въехал, — огромное усилие на проблему, которой у тебя пока нет.

Знаменитая фраза Дональда Кнута — «**преждевременная оптимизация (premature optimization) — корень всех зол**» — предостерегает от оптимизации до того, как ты знаешь, что́ и нужно ли вообще ускорять.

Оптимизировать код, который не медленный или даже редко запускается, не даёт ничего и стоит немало: сложность, баги и время, украденное у того, что важно. Большинству кода не нужно быть быстрым — ему нужно быть верным и ясным. Работа над скоростью — для частей, которые измерение доказало **горячими (hot)**.

_Diagram: Из двенадцати модулей горячий один — его и ускоряем. Остальные держим верными и ясными._

#### Premature Optimization (преждевременная оптимизация)

- **Что это:** Оптимизировать до того, как измерение показало, что это нужно.
- **Как работает:** Ты платишь ясностью и временем за скорость в месте, где она никому не нужна.
- **Пример:** Неделя на хитрый кэш для страницы настроек, которую открывают раз в месяц.
- **Подвох:** Запутанный «быстрый» код мешает, когда настоящее узкое место всплывает в другом месте.
- **Связи:** Лечится **Profiling**; объясняется **Amdahl's Law**; не путать с **Performance by Design**.

### Correct → Clear → Fast — сначала верно и ясно, быстро там, где важно

> Ты пишешь рецепт так, чтобы любой мог по нему готовить, и упрощаешь только тот шаг, что делаешь сто раз в день, — не каждый шаг, только горячий.

Порядок такой: сделай **верным (correct)**, сделай **ясным (clear)** и сделай **быстрым (fast) только там, где измерение показывает, что это важно**.

Ясный код легче оптимизировать потом — именно потому, что ты его понимаешь. Запутанный «быстрый» код трудно чинить, когда настоящее узкое место появляется в другом месте. Скорость почти всегда стоит читаемости, так что эту цену платишь только там, где она покупает реальный, измеренный выигрыш, а остальное держишь простым.

_Diagram: Порядок шагов: сначала верно, потом ясно, и только потом быстро — и только там, где измерено._

#### Correct → Clear → Fast (make it work, make it right, make it fast)

- **Что это:** Порядок приоритетов: верность, ясность, и лишь потом скорость.
- **Как работает:** Скорость добавляешь точечно в горячих местах, когда ясный код уже есть и измерен.
- **Пример:** Пишешь простой понятный обработчик; профиль показал горячий цикл — оптимизируешь только его.
- **Подвох:** Оптимизация почти всегда стоит читаемости — трать её только на выигрыш.
- **Связи:** Противоядие от **Premature Optimization**.

### Performance by Design — но не игнорируй производительность в дизайне

> Ты не делаешь сейсмоустойчивым сарай, но и не строишь небоскрёб на песке. Некоторые решения дёшево принять верно рано и разорительно чинить поздно.

Предупреждение — не «никогда не думай о производительности». Выбрать алгоритм **O(n²)** там, где **O(n)** был так же лёгок, или структуру, которая не масштабируется, — это ошибка дизайна, за которую дорого заплатишь потом.

Баланс такой: не микрооптимизируй рано, но и не делай архитектурных выборов, которые медленны по дизайну и дороги в откате. **Большую форму** выбери верно и дёшево заранее; **детали** оптимизируй, только когда измерение требует.

_Diagram: Слева — решения, которые принимают сразу: алгоритм, структура данных, форма архитектуры. Справа — то, что тюнят потом, по профилю._

#### Performance by Design (производительность в дизайне)

- **Что это:** Большие решения о форме системы сразу принимать с оглядкой на скорость.
- **Как работает:** Алгоритм, структура данных и архитектура выбираются рано; микротюнинг — потом, по замерам.
- **Пример:** Сразу выбрать поиск по индексу, а не перебор всей таблицы, — это не преждевременно.
- **Подвох:** Легко спрятать за этим принципом обычную преждевременную оптимизацию деталей.
- **Связи:** Граница **Premature Optimization**; главный инструмент — **Better Algorithm**.

> **Вопрос сдержанности:** «Я оптимизирую это, потому что измерение показало реальное узкое место, — или просто потому, что могу, меняя ясность на скорость, которая никому не нужна?»

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

## 7. Применять хорошо

Хорошая работа над производительностью — спокойный повторяемый метод, а не героический аврал. Вся практика умещается в короткий цикл, который ты запускаешь, только когда есть реальная причина.

### Optimization Loop — измерь, почини узкое место, повтори

> Механик диагностирует приборами, чинит одну отказавшую деталь, потом проверяет снова — и никогда не меняет детали наугад в надежде, что стук пропадёт.

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

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

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

#### Optimization Loop (цикл оптимизации)

- **Что это:** Повторяющийся цикл: измерить, найти, починить одно, проверить.
- **Как работает:** Каждый круг — один **Measure–Fix–Measure** против текущего **Limiting Step**.
- **Пример:** Круг 1: индекс на запрос. Круг 2: рендер стал узким местом — кэш шаблона. Цель достигнута — стоп.
- **Подвох:** Без цели цикл никогда не заканчивается.
- **Связи:** Собирает **Profiling**, **Bottleneck** и **Performance Target** вместе.

### Performance Target — поставь цель, потом остановись

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

У производительности нет естественного конца — всегда можно сделать чуть быстрее. Так что поставь **цель (performance target)** — «страницы быстрее 300 мс на p99» — и остановись, когда достиг.

Без цели оптимизация превращается в бесконечную яму времени с убывающей отдачей, а сложность вползает ради выигрышей, которых никто не замечает. Знать, что значит «достаточно быстро» для твоих пользователей и твоего случая, — вот что позволяет сделать ровно столько работы, сколько нужно, и пойти строить что-то ещё.

_Diagram: p99 падает с каждым кругом. Как только кривая ушла под цель — стоп; дальше только убывающая отдача._

#### Performance Target (цель производительности, бюджет)

- **Что это:** Конкретное число «достаточно быстро», привязанное к перцентилю.
- **Как работает:** Цикл оптимизации идёт, пока метрика выше цели, и останавливается, когда ниже.
- **Пример:** «p99 главной страницы — меньше 300 мс».
- **Подвох:** Цель на среднем обманет — ставь её на p99.
- **Связи:** Выход из **Optimization Loop**; меряется через **Percentiles**.

### Как всё связано

> Карта маршрута целиком: видно, откуда начинаешь, через какие станции идёшь и где выходишь.

Понятия курса складываются в одну картину. **Профилирование** находит **узкое место**; цель — **latency** или **throughput** — говорит, что именно чинить. Узкое место обычно лечится одной из четырёх классик: **лучший алгоритм**, **меньше круговых поездок**, **кэш** или **меньше работы**. Потом — измерить снова, по **перцентилям**, против **цели**, и остановиться. А **преждевременная оптимизация** — то, что случается, когда пропускаешь измерение.

_Diagram: Профиль → узкое место → одна из классических починок → измерить снова → цель на p99 останавливает цикл. Внизу — что бывает без измерения._

#### Прежде чем оптимизировать

- **Измерил ли я**, куда на деле уходит время, или гадаю?
- **Задержка или пропускная способность** — какой «быстро» мне правда нужен?
- **Где узкое место** — та одна часть, что доминирует во времени?
- **Это классика** — плохой алгоритм, слишком много круговых поездок, нет кэша, лишняя работа?
- **Слежу ли я за p99**, а не только за средним, которое прячет медленный хвост?
- **Есть ли цель**, к которой я оптимизирую, чтобы знать, когда остановиться?

#### Признаки, что ты оптимизируешь хорошо

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

## Шпаргалка: все 20 понятий

### Измерение

- **Profiling** (профилирование): Измерить, где программа на деле тратит время, функция за функцией.
- **Measure–Fix–Measure** (измерь, почини, измерь): Каждая починка — гипотеза, которую подтверждает второе измерение.

### Цели скорости

- **Latency** (задержка): Время одной операции от начала до конца — то, что чувствует пользователь.
- **Throughput** (пропускная способность): Сколько работы система обрабатывает в секунду.
- **Batching** (батчинг): Группировать работу: пропускная способность растёт, задержка тоже.

### Узкое место

- **Bottleneck** (узкое место): Одна часть, которая занимает бо́льшую часть времени.
- **Limiting Step** (ограничивающий шаг): Система быстра, как её самый медленный шаг; починил — появится следующий.
- **Amdahl's Law** (закон Амдала): Выигрыш ограничен долей времени ускоряемой части.

### Крупнейшие выигрыши

- **Better Algorithm** (Big-O): Сменить подход: O(n²) → O(n) бьёт любое железо.
- **N+1 Query** (лишние round trips): Запрос на каждый элемент списка; лечится выборкой оптом.
- **Caching** (кэширование): Запомнить дорогой результат и не считать его снова.
- **Doing Less** (lazy evaluation): Не делать ненужную работу: лениво, только нужные данные, без выброшенных результатов.

### Хвост

- **Average (Mean)** (среднее): Одно спокойное число, которое прячет медленные случаи.
- **Percentiles** (p50 / p99): p50 — типичный случай, p99 — примерно худший опыт реального пользователя.
- **Tail Latency** (хвостовая задержка): Самые медленные запросы; их помнят, и при многих вызовах их ловит почти каждый.

### Сдержанность

- **Premature Optimization** (преждевременная оптимизация): Ускорять до того, как измерение показало нужду.
- **Correct → Clear → Fast** (make it work, right, fast): Сначала верно, потом ясно, быстро — только в горячих местах.
- **Performance by Design** (производительность в дизайне): Большую форму (алгоритм, структуру, архитектуру) выбирай верно сразу.

### Практика

- **Optimization Loop** (цикл оптимизации): Измерь → почини одно узкое место → измерь снова → повтори.
- **Performance Target** (цель): Число «достаточно быстро» на p99 — где цикл останавливается.

> Производительность — это спокойный цикл: измерь, почини одно узкое место, измерь снова, остановись на цели. Тянись к скорости только там, где доказано, что она важна, а всё остальное держи ясным.

Конец экспресс-курса · 7 глав · 20 понятий

Дальше — практика: возьми что-то, что ощущается медленным, и вместо гадания правда профилируй — найди ту одну функцию или запрос, что доминирует, почини только это и измерь до и после. Потом проверь p99, а не только среднее. Метод доказывает себя в тот миг, когда одна прицельная починка, найденная измерением, делает больше, чем неделя чутья. Держи одну мысль выше остальных: сначала измерь. Почти любая катастрофа производительности — это кто-то, кто оптимизировал то, что угадал, а не то, что измерил. А почти любой реальный выигрыш — это найти то одно узкое место, которое было важно, и починить ровно его.
