🐮 Cow: Ленивое клонирование
Все знают: лишний
Допустим, мы чистим инпут от спецсимволов.
1. Если спецсимволов нет - зачем аллоцировать новую строку? Можно вернуть ссылку на исходную.
2. Если есть - придется создать новую
Здесь на сцену выходит
Это мощнейший инструмент для хот-пасов (hot paths), где 90% данных не требуют изменений. Не ленитесь использовать
#rust #performance #std
👉 @rust_lib
Все знают: лишний
clone() - это грех. Но иногда нам нужно вернуть строку, которая может быть изменена, а может и нет.Допустим, мы чистим инпут от спецсимволов.
1. Если спецсимволов нет - зачем аллоцировать новую строку? Можно вернуть ссылку на исходную.
2. Если есть - придется создать новую
String.Здесь на сцену выходит
std::borrow::Cow (Clone on Write).
use std::borrow::Cow;
fn sanitize(input: &str) -> Cow<str> {
if input.contains('!') {
// Аллокация происходит только тут
Cow::Owned(input.replace('!', "?"))
} else {
// Тут мы просто возвращаем ссылку. Zero allocation.
Cow::Borrowed(input)
}
}
Это мощнейший инструмент для хот-пасов (hot paths), где 90% данных не требуют изменений. Не ленитесь использовать
Cow там, где можно избежать лишних аллокаций.#rust #performance #std
👉 @rust_lib
👍31❤5🥰1😁1🤗1
Почему
📈 Векторный рост: Избегаем реаллокаций
Мы часто пишем:
Что происходит под капотом?
1. Вектор создается пустым (0 байт).
2. Первый
3. Пятый
• Создается новый буфер (размером x2).
• Все старые элементы копируются туда.
• Старый буфер освобождается.
• Новый элемент записывается.
Это называется Reallocation. Это дорого. Если у вас 10 миллионов элементов, вы сделаете десятки реаллокаций и скопируете гигабайты данных просто так.
Решение: Если вы хотя бы примерно знаете размер - подскажите компилятору.
Правило:
#rust #performance #vectors
👉 @rust_lib
Vec::new() это ловушка для производительности.📈 Векторный рост: Избегаем реаллокаций
Мы часто пишем:
let mut vec = Vec::new();
for item in heavy_iter {
vec.push(item);
}
Что происходит под капотом?
1. Вектор создается пустым (0 байт).
2. Первый
push: аллокация на 4 элемента (условно).3. Пятый
push: места нет.• Создается новый буфер (размером x2).
• Все старые элементы копируются туда.
• Старый буфер освобождается.
• Новый элемент записывается.
Это называется Reallocation. Это дорого. Если у вас 10 миллионов элементов, вы сделаете десятки реаллокаций и скопируете гигабайты данных просто так.
Решение: Если вы хотя бы примерно знаете размер - подскажите компилятору.
// Идеально, если знаем точно
let mut vec = Vec::with_capacity(exact_count);
// Или используем итераторы, они часто сами знают свой размер
let vec: Vec<_> = heavy_iter.collect();
collect() умный. Он смотрит на size_hint итератора и сразу аллоцирует нужный буфер (если итератор "честный").Правило:
Vec::new() - только если вы правда не знаете, будет ли там хоть один элемент. В остальных случаях - with_capacity.#rust #performance #vectors
👉 @rust_lib
👍28❤5🥰2😢2🤗1
🗝 Турбируем HashMap
Стандартный
Почему? Он криптографически стоек к HashDoS атакам (когда злоумышленник подбирает ключи так, чтобы все они попадали в один бакет, превращая мапу в связный список и убивая CPU).
Но SipHash медленный. Если вы решаете алгоритмические задачи, пишете геймдев или у вас внутренний сервис без внешнего доступа - вам не нужна защита от DoS. Вам нужна скорость.
Используйте сторонние хэшеры. Самые популярные:
1.
2.
Пример с крейтом
Прирост производительности на операциях вставки/поиска может достигать 2x-3x на простых ключах.
Итог:
• Backend наружу? Оставляем дефолтный
• GameDev / Algo / Internal Data Processing? Ставим
#rust #performance #hashmap #external_crates
👉 @rust_lib
Стандартный
std::collections::HashMap в Rust использует алгоритм хэширования SipHash.Почему? Он криптографически стоек к HashDoS атакам (когда злоумышленник подбирает ключи так, чтобы все они попадали в один бакет, превращая мапу в связный список и убивая CPU).
Но SipHash медленный. Если вы решаете алгоритмические задачи, пишете геймдев или у вас внутренний сервис без внешнего доступа - вам не нужна защита от DoS. Вам нужна скорость.
Используйте сторонние хэшеры. Самые популярные:
1.
FxHash (rustc-hash) - используется внутри самого компилятора Rust и Firefox. Молниеносно быстрый для коротких ключей (integers).2.
AHash - современный, быстрый, использует аппаратные инструкции AES.Пример с крейтом
rustc-hash:
use rustc_hash::FxHashMap;
// Это та же HashMap, но с быстрым хэшером
let mut map: FxHashMap<u32, &str> = FxHashMap::default();
Прирост производительности на операциях вставки/поиска может достигать 2x-3x на простых ключах.
Итог:
• Backend наружу? Оставляем дефолтный
SipHash.• GameDev / Algo / Internal Data Processing? Ставим
FxHash или AHash.#rust #performance #hashmap #external_crates
👉 @rust_lib
👍17❤5🥰2🏆1
🔮 Ты не пройдешь: Магия предсказателя ветвлений
Вы когда-нибудь задумывались, почему обработка отсортированного массива чисел происходит в разы быстрее, чем случайного, даже если логика одна и та же?
Всё дело в конвейере (pipeline). Процессор выполняет инструкции не по одной, а потоком: пока одна декодируется, другая уже выполняется. Но тут появляется
Процессор не знает, пойдет код в
• Угадал? Выполнение продолжается без задержек.
• Не угадал? Происходит Pipeline Flush. Процессор выбрасывает все инструкции, которые успел "набрать" по неверному пути, и начинает заново с правильного адреса. Это огромная потеря тактов (10-20 циклов CPU).
Пример на Rust:
На случайных данных BPU ошибается в 50% случаев. На сортированных почти никогда.
Вывод: Чем предсказуемее ваши данные, тем быстрее работает ваш код. Иногда
#rust #cpu #lowlevel #performance #branch_prediction
👉 @rust_lib
Вы когда-нибудь задумывались, почему обработка отсортированного массива чисел происходит в разы быстрее, чем случайного, даже если логика одна и та же?
Всё дело в конвейере (pipeline). Процессор выполняет инструкции не по одной, а потоком: пока одна декодируется, другая уже выполняется. Но тут появляется
if (ветвление).Процессор не знает, пойдет код в
then или в else, пока не вычислит условие. Но ждать он не может - конвейер встанет. Поэтому он угадывает.• Угадал? Выполнение продолжается без задержек.
• Не угадал? Происходит Pipeline Flush. Процессор выбрасывает все инструкции, которые успел "набрать" по неверному пути, и начинает заново с правильного адреса. Это огромная потеря тактов (10-20 циклов CPU).
Пример на Rust:
// Если data отсортирован: T T T T T F F F F F (паттерн ясен)
// Если data случайный: T F T T F F T F (паттерн непредсказуем)
for &x in &data {
if x > 128 {
sum += x;
}
}
На случайных данных BPU ошибается в 50% случаев. На сортированных почти никогда.
Вывод: Чем предсказуемее ваши данные, тем быстрее работает ваш код. Иногда
data.sort() перед обработкой окупается с лихвой.#rust #cpu #lowlevel #performance #branch_prediction
👉 @rust_lib
👍16👎3