← На главную

Памятка по профилированию кода на Rust

Недавно мы рассмотрели программу на Rust, чья производительность была не так высока, как хотелось бы. Озвучивалось несколько версий о том, как такое могло произойти, однако истинная причина так и осталась загадкой. Я подумал, что это отличная возможность познакомиться со средствами профилирования кода на Rust.

Напомню, что программа парсила логи Nginx при помощи регулярных выражений:

use regex::Regex; use std::collections::HashMap; use std::io::{self, BufRead}; const RE: &str = r#"^(\S+) \S+ \S+ \[([^\]]+)\] "(\S+) (\S+) \S+" (\d+)"#; fn main() { let regex = Regex::new(RE).unwrap(); let mut html_requests = HashMap::new(); // берем лок один раз, чтобы read_line() не брал-отпускал его постоянно let mut stdin = io::stdin().lock(); // переиспользуемый буфер уменьшает количество аллокаций памяти let mut input = String::new(); loop { input.clear(); // очищаем строку, но не уменьшаем capacity let Ok(bytes_read) = stdin.read_line(&mut input) else { break; }; if bytes_read == 0 { break; } let captures = match regex.captures(&input) { Some(captures) => captures, None => continue, }; let method = &captures[3]; let url = &captures[4]; let status_code: u32 = captures[5].parse().unwrap_or(0); if status_code == 200 && method == "GET" && url.ends_with(".html") { match html_requests.get_mut(url) { Some(count) => *count += 1, None => { html_requests.insert(url.to_string(), 1); } } } } // into_iter() - перемещает данные из HashMap, а не копирует их // collect() - возвращает вектор кортежей (URL, счетчик) let mut pairs: Vec<_> = html_requests.into_iter().collect(); // используем неустойчивую сортировку pairs.sort_unstable_by_key(|(_, count)| std::cmp::Reverse(*count)); for (url, count) in pairs { println!("{} {}", count, url); } }

Данный код отличается от опубликованного изначально. Бывалые программисты на Rust в лице @klebed и @bemyak предложили пару оптимизаций, см комментарии к коду. Увы, к большому ускорению данные оптимизации не привели. Разница в скорости между исходной программой и оптимизированной находится в пределах погрешности измерения. Раз такое дело, будем искать батлнек инструментально.

Далее предполагается, что читатель имеет некоторый опыт профилирования кода на C/C++, и объяснять, что такое perf и флеймграфы, не требуется. Если это не так, обратите внимание на статью Профилирование кода на C/C++ в Linux и FreeBSD.

Для Cargo есть удобный плагин flamegraph, которым мы и воспользуемся. Устанавливается он так:

$ cargo install flamegraph

Плагин работает под Windows, Linux и MacOS. Поскольку в настоящее время я сижу под Linux, то далее речь пойдет исключительно о нем.

Профилирование кода осуществляется на release-сборках. По умолчанию данные сборки не включают отладочные символы. Чтобы это исправить, в Cargo.toml дописываем:

[profile.release] debug = true

Далее говорим:

$ cargo clean $ cargo build -r $ cargo flamegraph --bin nginx_log_analyzer < ./eaxme-2025-12.log

Лично я в первый раз получил ошибку:

Access to performance monitoring and observability operations is limited. Consider adjusting /proc/sys/kernel/perf_event_paranoid setting ... (... пропущено ...)

По умолчанию в Ubuntu 24.04 пользователи не могут профилировать даже собственные процессы. Исправим это, сказав:

$ sudo sysctl -w kernel.perf_event_paranoid=1

А чтобы настройка не слетала после перезагрузки, в /etc/sysctl.conf допишем:

kernel.perf_event_paranoid=1

Повторяем команду cargo flamegraph ... и получаем симпатичный файл flamegraph.svg, вроде такого (кликабельно):

Пример флеймграфа, построенного при профилировании кода на Rust

Изучив четвертую строчку снизу, мы понимаем, что программа проводит ~70% времени в зависимостях модуля regex. Таким образом, мы установили, что бутылочном горлышком является конкретная реализация регулярных выражений. Чтобы ускорить программу, следует либо переписать ее без регулярных выражений, либо попробовать байндинги к условному libpcre.

Как видите, пользоваться cargo flamegraph – одно удовольствие. Больше информации можно почерпнуть из документации к плагину на crates.io и cargo flamegraph --help.