- Неэффективные аллокации
- Дублирование операций
- Алгоритмическая сложность
- Избыточное копирование
- Блокирующие операции
Проблема:
// ❌ МЕДЛЕННО - миллионы аллокаций
fn process_items(items: &[Item]) -> Vec<String> {
let mut result = Vec::new(); // Начальная capacity = 0
for item in items {
result.push(item.to_string()); // Каждый раз может реаллоцировать!
}
result
}Как найти:
rg "Vec::new\(\)" --type rust # Ищем Vec::new() без with_capacityПравильно:
// ✅ БЫСТРО - одна аллокация
fn process_items(items: &[Item]) -> Vec<String> {
let mut result = Vec::with_capacity(items.len()); // Сразу выделяем память
for item in items {
result.push(item.to_string()); // Никогда не реаллоцирует
}
result
}
// 🚀 ЕЩЁ БЫСТРЕЕ - используем итераторы
fn process_items_fast(items: &[Item]) -> Vec<String> {
items.iter().map(|item| item.to_string()).collect()
// collect() умный - сам выделит правильный размер
}Измеримый эффект:
- Для 10,000 элементов: 2-3x быстрее
- Для 100,000 элементов: 5-10x быстрее
Проблема:
// ❌ МЕДЛЕННО - создает новые String
fn build_url(host: &str, path: &str, query: &str) -> String {
let mut url = String::new();
url.push_str("https://");
url.push_str(host);
url.push('/');
url.push_str(path);
url.push('?');
url.push_str(query);
url
}Правильно:
// ✅ БЫСТРО - одна аллокация нужного размера
fn build_url(host: &str, path: &str, query: &str) -> String {
let capacity = 8 + host.len() + 1 + path.len() + 1 + query.len();
let mut url = String::with_capacity(capacity);
url.push_str("https://");
url.push_str(host);
url.push('/');
url.push_str(path);
url.push('?');
url.push_str(query);
url
}
// 🚀 ИЛИ используй format! для коротких строк
fn build_url_simple(host: &str, path: &str, query: &str) -> String {
format!("https://{}/{}?{}", host, path, query)
}Проблема:
// ❌ МЕДЛЕННО - создает промежуточный Vec
fn sum_even_squares(nums: &[i32]) -> i32 {
let evens: Vec<_> = nums.iter().filter(|&n| n % 2 == 0).collect(); // ❌ Аллокация!
let squares: Vec<_> = evens.iter().map(|&n| n * n).collect(); // ❌ Еще одна!
squares.iter().sum()
}Правильно:
// ✅ БЫСТРО - zero allocations
fn sum_even_squares(nums: &[i32]) -> i32 {
nums.iter()
.filter(|&n| n % 2 == 0)
.map(|&n| n * n)
.sum()
// Никаких промежуточных Vec - все в одном проходе!
}Эффект:
- Для 1,000,000 элементов: 10-20x быстрее
- 0 аллокаций vs 2 больших аллокации
Проблема:
// ❌ МЕДЛЕННО - парсим initData ДВА раза
pub fn validate_telegram_data(raw: &str, hash: &str, bot_token: &str) -> bool {
let parsed = parse_query_string_raw(raw); // 1-й раз
validate_auth_date(&parsed);
let computed_hash = compute_hash(raw, bot_token); // Внутри еще раз парсит!
// ^^^
}
fn compute_hash(raw: &str, bot_token: &str) -> String {
let parsed = parse_query_string_raw(raw); // 2-й раз ❌
// ...
}Решение:
// ✅ БЫСТРО - парсим один раз
pub fn validate_telegram_data(raw: &str, hash: &str, bot_token: &str) -> bool {
let parsed = parse_query_string_raw(raw); // Один раз!
validate_auth_date(&parsed);
let computed_hash = compute_hash(&parsed, bot_token); // Передаем ссылку
// ^^^^^^
}
fn compute_hash(parsed: &BTreeMap<String, String>, bot_token: &str) -> String {
// Используем уже распарсенные данные
}Эффект:
- -50% времени на парсинг
- Особенно важно для горячих путей (вызывается часто)
1. Поиск одинаковых вызовов функций:
# Найти функции, которые вызываются > 1 раза в одной функции
rg "(\w+)\(.*\).*\1\(" --type rust2. Визуальный поиск:
- Ищи одинаковые вычисления внутри циклов
- Проверяй, не вызываются ли тяжелые функции несколько раз с одинаковыми аргументами
Пример:
// ❌ ПЛОХО - вычисляет len() на каждой итерации
for i in 0..items.len() {
if i < items.len() / 2 { // len() вызывается снова!
// ...
}
}
// ✅ ХОРОШО
let len = items.len();
let half = len / 2;
for i in 0..len {
if i < half {
// ...
}
}Проблема:
// ❌ O(n²) - для каждого элемента проходим весь Vec
fn remove_duplicates(items: Vec<String>) -> Vec<String> {
let mut result = Vec::new();
for item in items {
if !result.contains(&item) { // ❌ O(n) для каждого элемента!
result.push(item);
}
}
result
}Правильно:
use std::collections::HashSet;
// ✅ O(n) - используем HashSet
fn remove_duplicates(items: Vec<String>) -> Vec<String> {
let mut seen = HashSet::new();
let mut result = Vec::new();
for item in items {
if seen.insert(item.clone()) { // O(1) в среднем
result.push(item);
}
}
result
}
// 🚀 ЕЩЁ ПРОЩЕ
fn remove_duplicates_simple(items: Vec<String>) -> Vec<String> {
items.into_iter().collect::<HashSet<_>>().into_iter().collect()
}Эффект:
- Для 1,000 элементов: 100x быстрее
- Для 10,000 элементов: 1000x быстрее
Проблема:
// ❌ O(n) для каждого поиска
fn find_user_by_id(users: &[User], id: i64) -> Option<&User> {
users.iter().find(|u| u.id == id) // Проходит весь массив!
}
// В цикле - катастрофа:
for id in user_ids {
let user = find_user_by_id(&all_users, id); // O(n) каждый раз!
}
// Итого: O(m * n) где m = user_ids.len(), n = all_users.len()Правильно:
use std::collections::HashMap;
// ✅ O(1) для каждого поиска
fn build_user_map(users: Vec<User>) -> HashMap<i64, User> {
users.into_iter().map(|u| (u.id, u)).collect()
}
let user_map = build_user_map(all_users); // O(n) один раз
for id in user_ids {
let user = user_map.get(&id); // O(1) каждый раз!
}
// Итого: O(n + m) вместо O(m * n)Эффект для 10,000 поисков в массиве из 10,000 элементов:
- Было: ~50,000,000 операций (O(m*n))
- Стало: ~20,000 операций (O(n+m))
- 2500x быстрее!
1. Вложенные циклы:
rg "for.*\{[\s\S]*?for" --type rust # Ищем вложенные циклы2. Vec::contains в цикле:
rg "\.contains\(" --type rust3. Linear search patterns:
rg "\.iter\(\)\.find|\.iter\(\)\.position" --type rustВопросы к себе:
- Сколько раз выполняется самая внутренняя операция?
- Если данных станет в 10x больше, во сколько раз медленнее?
- Можно ли использовать HashMap/HashSet/BTreeMap?
Проблема:
// ❌ МЕДЛЕННО - копирует весь Vec
fn process_items(items: Vec<String>) -> usize {
let copy = items.clone(); // ❌ Зачем?
copy.len()
}Как найти:
rg "\.clone\(\)" --type rust # Проверь каждый clone!Правильно:
// ✅ БЫСТРО - используем ссылку
fn process_items(items: &[String]) -> usize {
items.len()
}Проблема:
// ❌ МЕДЛЕННО - миллионы аллокаций
fn log_items(items: &[Item]) {
for item in items {
tracing::debug!("Item: {}", item.id.to_string()); // ❌ Лишний to_string()
}
}Правильно:
// ✅ БЫСТРО - Display сам форматирует
fn log_items(items: &[Item]) {
for item in items {
tracing::debug!("Item: {}", item.id); // Без to_string()
}
}Пример из нашего проекта:
// ❌ БЫЛО - копирует String'и
let mut kv_pairs: Vec<(String, String)> = parsed.into_iter()...
// ✅ СТАЛО - zero-copy
let mut kv_pairs: Vec<(&String, &String)> = parsed.iter()...Эффект:
- Для 100 пар ключ-значение по 20 символов: ~4KB vs ~0 байт аллокаций
- 3-5x быстрее
Проблема:
// ❌ БЛОКИРУЕТ весь runtime!
async fn handle_request() -> Result<String> {
let data = std::fs::read_to_string("file.txt")?; // ❌ Sync I/O!
Ok(data)
}Правильно:
// ✅ Асинхронное чтение
async fn handle_request() -> Result<String> {
let data = tokio::fs::read_to_string("file.txt").await?;
Ok(data)
}
// Или вынеси в blocking pool
async fn handle_request_alt() -> Result<String> {
tokio::task::spawn_blocking(|| {
std::fs::read_to_string("file.txt")
}).await?
}Проблема:
// ❌ БЛОКИРУЕТ другие задачи
async fn expensive_computation(n: u64) -> u64 {
(0..n).sum() // Может работать миллисекунды/секунды
}Правильно:
// ✅ В отдельном потоке
async fn expensive_computation(n: u64) -> u64 {
tokio::task::spawn_blocking(move || {
(0..n).sum()
}).await.unwrap()
}cargo install flamegraph
cargo flamegraph --bin your-app
# Откроется flamegraph.svg - визуализация где проводится время# Добавь в Cargo.toml:
# [dev-dependencies]
# criterion = "0.5"
# benches/my_benchmark.rs
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn benchmark_parse(c: &mut Criterion) {
let data = "auth_date=123&user=test&hash=abc";
c.bench_function("parse_query_string", |b| {
b.iter(|| parse_query_string_raw(black_box(data)))
});
}
criterion_group!(benches, benchmark_parse);
criterion_main!(benches);cargo benchcargo install cargo-asm
cargo asm your_crate::function_name- Vec::new() заменен на Vec::with_capacity() где известен размер?
- Нет лишних .clone() и .to_string()?
- Используются итераторы вместо промежуточных Vec?
- String::with_capacity() для конкатенации?
- Нет O(n²) где можно O(n)?
- HashMap вместо linear search?
- Нет вложенных циклов по большим данным?
- Правильно выбраны структуры данных?
- Используются ссылки (&T) вместо owned (T)?
- Нет лишних .clone()?
- Избегаются .to_owned()/.to_string() в горячих путях?
- Нет блокирующих операций в async?
- Долгие вычисления в spawn_blocking?
- Используется async I/O?
- Нет дублирования вычислений?
- Профилирование показало проблемы?
- Бенчмарки проходят в пределах нормы?
- Профилирование показало проблему (не оптимизируй наугад!)
- Это горячий путь (вызывается часто)
- Измеримый эффект (минимум 10% улучшение)
- Холодные пути (startup, редкие операции)
- Код, который уже достаточно быстр
- Если это ухудшит читаемость без реального выигрыша
Правило: "Premature optimization is the root of all evil" - Donald Knuth
Но: "Premature pessimization is also evil" - правильные структуры данных с самого начала.