Ресурсопотребление и метрики
Ресурсопотребление и метрики
Ресурсы
Ресурсы вычислительной системы — это ограниченные компоненты, необходимые для выполнения программ.
Основные категории ресурсов:
- процессорное время (CPU time)
- оперативная память (RAM)
- дисковое пространство и операции ввода-вывода
- сетевые ресурсы
- специализированные ресурсы (GPU, TPU)
Ресурсы делятся на:
- вычислительные — процессор, сопроцессоры
- хранилища — оперативная память, дисковое пространство
- коммуникационные — сетевые интерфейсы, шины данных
Что измерять — CPU, память, дисковый I/O, сеть
Метрики производительности — количественные показатели использования ресурсов системы.
| Ресурс | Ключевые метрики | Единицы измерения |
|---|---|---|
| CPU | Использование, время в пользовательском/системном режиме, контекстные переключения | проценты, миллисекунды |
| Память | Используемая память, выделенная память, свопинг, частота сборки мусора | мегабайты, гигабайты |
| Диск | Операции ввода-вывода в секунду (IOPS), пропускная способность, задержка | операции/сек, МБ/с, мс |
| Сеть | Пропускная способность, задержка, количество пакетов, ошибки | Мбит/с, мс, пакеты/с |
CPU — на что обращать внимание
Процессорное время — основной ресурс для выполнения инструкций программы.
Важные аспекты анализа CPU:
-
Утилизация процессора
- 0-70% — нормальная нагрузка
- 70-90% — высокая нагрузка, возможны задержки
- 90-100% — критическая нагрузка, риск деградации производительности
-
Режимы выполнения
- Пользовательский режим — выполнение кода приложения
- Системный режим — выполнение системных вызовов ядром ОС Высокий процент системного времени может указывать на чрезмерное количество системных вызовов
-
Контекстные переключения Частые переключения между потоками создают накладные расходы. Норма — до нескольких тысяч переключений в секунду на ядро.
-
Прерывания Аппаратные прерывания от устройств могут конкурировать за процессорное время.
Пример анализа в Linux:
# Просмотр загрузки CPU
top
# Детальная статистика
vmstat 1
# Процент времени в разных режимах
mpstat -P ALL 1
Пример анализа в Windows через PowerShell:
# Загрузка CPU по процессам
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 Name, CPU
# Системная статистика
Get-Counter '\Processor(_Total)\% Processor Time'
Память — на что обращать внимание
Оперативная память — временно хранилище данных и кода во время выполнения программы.
Ключевые метрики памяти:
-
Используемая память Объём памяти, выделенный процессом. Включает:
- кучу (heap) — динамически выделяемая память
- стек (stack) — память для локальных переменных и вызовов
- сегмент кода — исполняемые инструкции
- сегмент данных — глобальные и статические переменные
-
Резидентная память (RSS) Фактически загруженная в физическую память часть процесса.
-
Виртуальная память Общий адресное пространство процесса, включая своп и зарезервированные области.
-
Свопинг Перемещение страниц памяти между оперативной памятью и диском. Частый свопинг указывает на нехватку оперативной памяти.
-
Утечки памяти Постепенный рост используемой памяти без освобождения.
Пример обнаружения утечки памяти в C#:
public class MemoryLeakExample
{
private static readonly List<byte[]> _cache = new List<byte[]>();
public void AddToCache()
{
// Добавляем данные в кэш, но никогда не удаляем старые
_cache.Add(new byte[10 * 1024 * 1024]); // 10 МБ
}
// Правильный вариант с ограничением размера кэша
public void AddToCacheWithLimit()
{
_cache.Add(new byte[10 * 1024 * 1024]);
if (_cache.Count > 100)
{
_cache.RemoveRange(0, _cache.Count - 100); // Оставляем последние 100 элементов
}
}
}
Пример мониторинга памяти в Python:
import tracemalloc
import gc
# Включение трассировки выделения памяти
tracemalloc.start()
# Выполнение кода
# ...
# Снимок текущего состояния
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print("[ Top 10 memory allocations ]")
for stat in top_stats[:10]:
print(stat)
# Принудительная сборка мусора
gc.collect()
Диск — на что обращать внимание
Дисковая подсистема — критический ресурс для операций ввода-вывода.
Важные метрики дисковой подсистемы:
-
IOPS (Input/Output Operations Per Second) Количество операций чтения/записи в секунду. Зависит от типа носителя:
- HDD: 50-200 IOPS
- SATA SSD: 10 000-100 000 IOPS
- NVMe SSD: 100 000-1 000 000+ IOPS
-
Пропускная способность Объём данных, передаваемых в секунду (МБ/с или ГБ/с).
-
Задержка (latency) Время от начала операции до её завершения:
- хороший показатель: < 10 мс
- приемлемый: 10-50 мс
- проблемный: > 50 мс
-
Очереди операций Накопление запросов на диск указывает на перегрузку подсистемы.
Пример анализа дисковой активности в Linux:
# Мониторинг в реальном времени
iostat -x 1
# Детальная статистика по устройствам
iotop
# Просмотр очередей
cat /proc/diskstats
Пример оптимизации дисковых операций:
// Плохо: множественные мелкие записи
using (var writer = new StreamWriter("log.txt"))
{
foreach (var item in items)
{
writer.WriteLine(item.ToString()); // Отдельная операция ввода-вывода
}
}
// Хорошо: пакетная запись с буферизацией
using (var writer = new StreamWriter("log.txt", bufferSize: 8192))
{
var batch = new StringBuilder();
foreach (var item in items)
{
batch.AppendLine(item.ToString());
if (batch.Length > 8192)
{
writer.Write(batch.ToString());
batch.Clear();
}
}
writer.Write(batch.ToString());
}
Сеть — на что обращать внимание
Сетевые ресурсы — каналы передачи данных между системами.
Ключевые сетевые метрики:
-
Пропускная способность Максимальный объём данных, передаваемых по сети в единицу времени.
-
Задержка (latency) Время прохождения пакета от отправителя к получателю:
- локальная сеть: 0.1-1 мс
- городская сеть: 1-10 мс
- межконтинентальная: 50-200 мс
-
Джиттер (jitter) Вариативность задержки. Высокий джиттер проблематичен для реального времени.
-
Потери пакетов Процент пакетов, не достигших получателя. Приемлемый уровень — < 1%.
-
Количество соединений Ограничение на количество одновременных TCP-соединений.
Пример мониторинга сети в Linux:
# Статистика сетевых интерфейсов
iftop
# Детальная статистика
nload
# Просмотр активных соединений
netstat -an | grep ESTABLISHED | wc -l
Пример оптимизации сетевых вызовов:
// Плохо: множество мелких запросов
public async Task<List<User>> GetUsersBad(List<int> userIds)
{
var users = new List<User>();
foreach (var id in userIds)
{
var user = await _httpClient.GetAsync($"api/users/{id}");
users.Add(await user.Content.ReadAsAsync<User>());
}
return users;
}
// Хорошо: пакетный запрос
public async Task<List<User>> GetUsersGood(List<int> userIds)
{
var response = await _httpClient.PostAsJsonAsync(
"api/users/batch",
new { userIds }
);
return await response.Content.ReadAsAsync<List<User>>();
}
Метрики кода — cyclomatic complexity, cognitive complexity, coupling, cohesion
Метрики качества кода — количественные показатели структуры и сложности программного кода.
| Метрика | Описание | Идеальное значение |
|---|---|---|
| Цикломатическая сложность | Количество линейно независимых путей выполнения | < 10 на метод |
| Когнитивная сложность | Сложность понимания кода человеком | < 15 на метод |
| Связность (coupling) | Степень зависимости между модулями | Минимальная |
| Связность (cohesion) | Степень объединения функциональности внутри модуля | Максимальная |
| Глубина наследования | Количество уровней в иерархии наследования | < 5 |
| Количество параметров | Аргументы метода | < 5 |
Пример высокой цикломатической сложности:
// Цикломатическая сложность: 8 (плохо)
public decimal CalculateDiscount(Order order, Customer customer, DateTime now)
{
if (customer.IsPremium)
{
if (order.Total > 1000)
return order.Total * 0.2m;
else
return order.Total * 0.15m;
}
else
{
if (order.Total > 1000)
{
if (now.DayOfWeek == DayOfWeek.Monday)
return order.Total * 0.1m;
else
return order.Total * 0.05m;
}
else
{
return 0;
}
}
}
Рефакторинг для снижения сложности:
// Цикломатическая сложность: 2 (хорошо)
public decimal CalculateDiscount(Order order, Customer customer, DateTime now)
{
var baseDiscount = GetBaseDiscount(customer, order.Total);
var dayBonus = GetDayBonus(now, order.Total);
return order.Total * (baseDiscount + dayBonus);
}
private decimal GetBaseDiscount(Customer customer, decimal total)
{
if (!customer.IsPremium) return 0;
return total > 1000 ? 0.15m : 0.1m;
}
private decimal GetDayBonus(DateTime now, decimal total)
{
if (total <= 1000) return 0;
return now.DayOfWeek == DayOfWeek.Monday ? 0.05m : 0;
}
Профилировщики: CPU profiling, memory profiling, allocation tracking
Профилировщик — инструмент для измерения производительности и потребления ресурсов программой.
Типы профилировщиков:
-
Сэмплинговые профилировщики Периодически снимают состояние стека вызовов. Низкие накладные расходы, но могут пропустить короткие вызовы.
-
Инструментирующие профилировщики Вставляют код измерения в каждый метод. Точны, но создают значительные накладные расходы.
-
Трассировочные профилировщики Записывают последовательность событий выполнения. Подходят для анализа временных зависимостей.
Пример использования профилировщика памяти в .NET:
using Система.Diagnostics;
// Создание снимка памяти
var snapshot1 = Process.GetCurrentProcess().WorkingSet64;
// Выполнение кода
ProcessData();
// Второй снимок
var snapshot2 = Process.GetCurrentProcess().WorkingSet64;
Console.WriteLine($"Использовано памяти: {(snapshot2 - snapshot1) / 1024 / 1024} МБ");
Пример использования профилировщика в Java (VisualVM):
// Запуск приложения с агентом профилирования
java -agentpath:/path/to/libprofiler.so=cpu=sample,alloc=5m MyApplication
// Или подключение к работающему процессу через JMX
Разбивка по стеку: attribution of resource usage to call paths
Атрибуция ресурсов по стеку вызовов — распределение потребления ресурсов между различными путями вызовов в программе.
Пример атрибуции памяти:
Выделено 100 МБ:
├─ ProcessOrders() — 60 МБ (60%)
│ ├─ ValidateOrder() — 10 МБ (10%)
│ ├─ CalculateTotal() — 30 МБ (30%)
│ │ └─ ApplyDiscounts() — 25 МБ (25%)
│ └─ SaveOrder() — 20 МБ (20%)
└─ GenerateReport() — 40 МБ (40%)
└─ FormatData() — 35 МБ (35%)
Инструменты для атрибуции:
- async-profiler (JVM) — атрибуция по стеку с низкими накладными расходами
- eBPF (Linux) — системная атрибуция без модификации приложения
- Windows Performance Toolkit — детальная атрибуция для Windows
Пример использования eBPF для атрибуции памяти:
# Трассировка выделений памяти с привязкой к стеку вызовов
bpftrace -e 'uprobe:/lib/x86_64-linux-gnu/libc.so.6:malloc { @[ustack()] = count(); }'
Бюджеты производительности: SLA, latency targets
Бюджет производительности — количественное ограничение на время выполнения операции или потребление ресурсов.
Типы бюджетов:
-
Временные бюджеты
- время отклика (latency): 95-й перцентиль < 200 мс
- время загрузки страницы: < 3 секунды
- время запуска приложения: < 5 секунд
-
Ресурсные бюджеты
- потребление памяти: < 500 МБ на процесс
- использование CPU: < 30% в среднем
- размер пакета: < 2 МБ
-
Сетевые бюджеты
- количество запросов: < 10 на страницу
- общий размер ресурсов: < 1.5 МБ
Пример определения бюджета в конфигурации:
{
"performanceBudget": {
"timeToInteractive": 3500,
"firstContentfulPaint": 1800,
"largestContentfulPaint": 2500,
"cumulativeLayoutShift": 0.1,
"totalBlockingTime": 200,
"resourceSizes": {
"total": 1572864,
"scripts": 524288,
"images": 786432
}
}
}
Пример мониторинга бюджета в коде:
public class PerformanceMonitor
{
private readonly Stopwatch _stopwatch = new Stopwatch();
private const long LatencyBudgetMs = 200;
public async Task<T> ExecuteWithMonitoring<T>(Func<Task<T>> operation)
{
_stopwatch.Restart();
var result = await operation();
_stopwatch.Stop();
var latency = _stopwatch.ElapsedMilliseconds;
if (latency > LatencyBudgetMs)
{
_logger.LogWarning(
"Превышен бюджет задержки: {Latency}мс > {Budget}мс",
latency,
LatencyBudgetMs
);
}
return result;
}
}
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). Возможно, для обычного пользователя слово производительность означает показатель скорости выполнения программы. Технически, это то, насколько эффективно система использует ресурсы, чтобы выполнить… Как возникают, что собой представляют и как с этим бороться. Работа переменных, их значения и работа с ними в отладке. Цепочка вызовов — это последовательность методов или функций, которые вызывают друг друга в процессе выполнения программы. Что такое мёртвый код, переменные, методы, классы, импорты. Культура производительности — совокупность ценностей, практик и инструментов, направленных на обеспечение высокой производительности системы и процессов разработки. Таким образом, битовые операции — это базовый слой, через который реализуется любая логика. Они позволяют напрямую манипулировать составом данных, не полагаясь на абстракции языков высокого уровня.… В конечном счёте, архитектура выполнения — это баланс между абстракцией и контролем. Чем выше уровень абстракции, тем проще писать код, но тем меньше контроля над ресурсами. Разработчик должен уметь… Чек-лист самопроверки — материал энциклопедии Вселенная IT.Архитектура выполнения программ
Ошибки, исключения и отказоустойчивость
Отладка и видимость состояния
Вызовы и иерархия
Неиспользуемый код и технический долг
Сборка и культура производительности
Битовые операции и низкоуровневое представление данных
Итоги
Чек-лист самопроверки