Добавить в корзинуПозвонить
Найти в Дзене
Цифровая Переплавка

🚀 Как разогнать парсер математических выражений на Rust в 44 раза: взгляд изнутри

Большинство программистов пишут парсеры — часто они оказываются слишком медленными, особенно когда речь идёт о больших данных. Но что, если применить к коду низкоуровневые оптимизации, многопоточность и даже SIMD-инструкции, чтобы добиться выдающихся результатов? В новой статье от Ricardo Pallas демонстрируется именно это: обычный парсер математических выражений, написанный на Rust, ускорили с 43 секунд до менее чем 1 секунды на файле размером 1.5 ГБ! 🔥 Обычный парсер работал медленно и неэффективно. Исходная реализация была стандартной: считывание строки, её разбиение на токены, создание вектора и рекурсивный разбор выражений: fn tokenize(input: &str) -> Vec<Token> {
input
.split_whitespace()
.map(|s| match s {
"+" => Token::Plus,
"-" => Token::Minus,
"(" => Token::OpeningParenthesis,
")" => Token::ClosingParenthesis,
n => Token::Operand(n.parse().unwrap()),
})
.collect()
} 🕐 Результат: 4
Оглавление
Файл весом в 1,5 ГБ преобразуется в поток символов, который летит через блоки “SIMD → AVX” к микрочипу: оранжевый логотип Rust на ноутбуке и молния подчёркивают, как оптимизации сократили парсинг с 43 секунд до менее секунды.
Файл весом в 1,5 ГБ преобразуется в поток символов, который летит через блоки “SIMD → AVX” к микрочипу: оранжевый логотип Rust на ноутбуке и молния подчёркивают, как оптимизации сократили парсинг с 43 секунд до менее секунды.

Большинство программистов пишут парсеры — часто они оказываются слишком медленными, особенно когда речь идёт о больших данных. Но что, если применить к коду низкоуровневые оптимизации, многопоточность и даже SIMD-инструкции, чтобы добиться выдающихся результатов? В новой статье от Ricardo Pallas демонстрируется именно это: обычный парсер математических выражений, написанный на Rust, ускорили с 43 секунд до менее чем 1 секунды на файле размером 1.5 ГБ! 🔥

📌 С чего всё началось?

Обычный парсер работал медленно и неэффективно. Исходная реализация была стандартной: считывание строки, её разбиение на токены, создание вектора и рекурсивный разбор выражений:

fn tokenize(input: &str) -> Vec<Token> {
input
.split_whitespace()
.map(|s| match s {
"+" => Token::Plus,
"-" => Token::Minus,
"(" => Token::OpeningParenthesis,
")" => Token::ClosingParenthesis,
n => Token::Operand(n.parse().unwrap()),
})
.collect()
}

🕐 Результат: 43 секунды обработки на 1.5 ГБ файле.

🎯 Пять шагов к невероятной оптимизации

Разработчик последовательно внедрил пять мощных оптимизаций:

1️⃣ Убрали лишние аллокации (43.1 с → 6.45 с, –85%)

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

2️⃣ Парсинг напрямую из байтов (6.45 с → 3.68 с, –43%)

Перешли с обработки строк (&str) на обработку сырых байтов (&[u8]). Это избавило от накладных расходов при работе со строками в Rust.

3️⃣ Упростили парсер, убрав Peekable (3.68 с → 3.21 с, –13%)

Отказались от предварительного просмотра токенов через адаптер Peekable, вместо этого сразу обрабатывали токены при поступлении.

4️⃣ Многопоточность и SIMD-инструкции (3.21 с → 2.21 с, –31%)

Использовали библиотеку Rayon для параллельного выполнения и SIMD-инструкции (AVX-512) для поиска оптимальных точек разделения данных. В чём именно суть?

  • SIMD-инструкции за одну команду проверяют 64 байта входных данных, обнаруживая позиции символов «+», «(» и «)».
  • Это позволило максимально эффективно найти подходящие места разбиения для обработки данных разными потоками.
  • В результате парсер разделяет данные безопасно (с учётом скобок и ассоциативности операций) и параллельно обрабатывает чанки.

5️⃣ Использовали memory-mapped файлы (2.21 с → 0.98 с, –56%)

Вместо чтения файла в оперативную память через fs::read применили технику memory mapping (mmap). Это позволяет:

  • 📥 Избежать копирования данных из пространства ядра в пользовательское пространство.
  • 🧵 Минимизировать негативный эффект false sharing (ложного разделения кэша между потоками).

Теперь ОС сама загружает и эффективно распределяет данные между потоками напрямую из диска.

🧠 Особые технические приёмы:

Помимо общих подходов, важную роль сыграли специальные низкоуровневые техники:

  • 🎛️ SIMD (Single Instruction, Multiple Data): одна инструкция процессора работает с большим объёмом данных одновременно. Так вместо последовательного просмотра каждого символа по отдельности можно проверить сразу 64 символа.
  • ⚙️ Эффективное разделение: алгоритм не просто дробит входные данные на равные части, а учитывает синтаксические и математические ограничения:
    ❌ Нельзя разбивать выражения внутри скобок.
    ❌ Нельзя разбивать выражения на операциях вычитания, т.к. это нарушает ассоциативность.
    ✅ Разбивать можно только на верхнем уровне, используя операции сложения.
  • 📁 mmap для I/O: memory-mapped файлы позволяют избегать лишних копий данных и дают значительный прирост производительности, особенно в многопоточных сценариях.

🔥 Итог: от 43 секунд к 0.98 секунды (ускорение в 44 раза!)

После всех оптимизаций парсер буквально преобразился:

-2

В итоге мы получили 44-кратное ускорение и подтвердили, что грамотно спроектированная низкоуровневая оптимизация на Rust — это инструмент, способный решить даже самые ресурсоёмкие задачи с фантастической эффективностью.

💡 Личное мнение автора (и моё тоже):

Разработчики, работающие на высоком уровне, часто забывают, какие потрясающие результаты можно получить, если глубоко погрузиться в детали железа и архитектуры процессора. Rust — язык, который идеально подходит для таких задач, предоставляя мощные инструменты для управления памятью и многопоточностью, сохраняя при этом удобство и безопасность кода. Стоит подчеркнуть, что подобные подходы актуальны не только для парсеров, но и в других высоконагруженных задачах.

Так что в следующий раз, когда ваш код работает слишком медленно, вспомните про этот пример и задайте себе вопрос: «Может быть, пора попробовать что-то подобное?»

🔗 Полезные ссылки и источники:

Теперь вы знаете, как вывести производительность вашего парсера на совершенно новый уровень! 🚀✨