Rust
2.2K subscribers
148 photos
101 videos
2 files
201 links
Полезный контент по программированию на Rust
Download Telegram
🐮 Cow: Ленивое клонирование

Все знают: лишний 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
👍315🥰1😁1🤗1
Почему 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
👍285🥰2😢2🤗1
🗝 Турбируем HashMap

Стандартный 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
👍175🥰2🏆1
🔮 Ты не пройдешь: Магия предсказателя ветвлений

Вы когда-нибудь задумывались, почему обработка отсортированного массива чисел происходит в разы быстрее, чем случайного, даже если логика одна и та же?

Всё дело в конвейере (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