🤠 Трюк Индианы Джонса:
Типичная ситуация: у вас есть
Компилятор бьет по рукам: нельзя просто так переместить (move) значение из-за мутабельной ссылки. Новички часто делают
Решение:
Оно забирает значение из ссылки, оставляя на его месте
Это работает для всего, что реализует
Это zero-cost, семантически верно и намного быстрее клонирования.
#rust #std #optimization #tips
👉 @rust_lib
std::mem::takeТипичная ситуация: у вас есть
&mut self, и вы хотите забрать поле (например, String или Vec), что-то с ним сделать, и, возможно, вернуть обратно или сохранить измененную версию.Компилятор бьет по рукам: нельзя просто так переместить (move) значение из-за мутабельной ссылки. Новички часто делают
.clone(), чтобы успокоить borrow checker. Но это лишняя аллокация!Решение:
std::mem::take.Оно забирает значение из ссылки, оставляя на его месте
Default::default().
struct Buffer {
data: Vec<u8>,
}
impl Buffer {
fn process(&mut self) {
// Мы "выкрадываем" вектор.
// В self.data теперь пустой Vec (без аллокаций).
let raw_data = std::mem::take(&mut self.data);
// Тяжелая обработка в другом потоке/функции,
// требующая владения (ownership)
let processed = heavy_transform(raw_data);
// Возвращаем результат
self.data = processed;
}
}
Это работает для всего, что реализует
Default (Option, String, Vec). Для типов без Default есть std::mem::replace.Это zero-cost, семантически верно и намного быстрее клонирования.
#rust #std #optimization #tips
👉 @rust_lib
🔥28👍9❤6❤🔥1🥰1😁1🤗1
⚡️ Забудь про RefCell для примитивов
Вам нужно изменить поле внутри неизменяемой структуры (
Рука тянется к
1. Хранит счетчик заимствований (borrow count).
2. В рантайме проверяет правила (может паниковать!).
Если ваш тип реализует
В чем магия
У него нет никакого рантайм-оверхеда. Вообще. Он не проверяет заимствования.
Он работает так: "Я даю тебе копию значения" (
Это атомарно? Нет. Это работает только в одном потоке. Но для однопоточного кода (или внутри
#rust #std #optimization #interior_mutability
👉 @rust_lib
Вам нужно изменить поле внутри неизменяемой структуры (
&self).Рука тянется к
RefCell? Стоп.RefCell имеет оверхед:1. Хранит счетчик заимствований (borrow count).
2. В рантайме проверяет правила (может паниковать!).
Если ваш тип реализует
Copy (числа, bool, маленькие структуры) - используйте Cell.В чем магия
Cell?У него нет никакого рантайм-оверхеда. Вообще. Он не проверяет заимствования.
Он работает так: "Я даю тебе копию значения" (
get) или "Я заменяю значение целиком" (set). Вы не можете получить ссылку (&) на содержимое Cell, поэтому правила Rust не нарушаются.
use std::cell::Cell;
struct Metric {
count: Cell<u64>, // Zero overhead
}
impl Metric {
fn inc(&self) {
// Мы меняем значение по неизменяемой ссылке &self
let current = self.count.get();
self.count.set(current + 1);
}
}
Это атомарно? Нет. Это работает только в одном потоке. Но для однопоточного кода (или внутри
thread_local) это самая быстрая мутабельность, которая только возможна.#rust #std #optimization #interior_mutability
👉 @rust_lib
👍13✍6❤1🥰1🤗1
🥶 Холодный расчет:
Компилятор Rust (LLVM) очень умный, но иногда мы знаем о поведении программы больше, чем он. Мы можем подсказать ему, какая ветка кода будет выполняться чаще.
1. Атрибут
Идеально для обработки ошибок. Вы говорите компилятору: "Сюда мы будем заходить крайне редко".
Компилятор сдвинет этот код в "дальний угол" бинарника, чтобы "горячий" путь шел линейно в памяти (это улучшает работу кэша инструкций и BPU).
2.
В стабильном Rust пока нет
Используйте
#rust #optimization #llvm #hints
👉 @rust_lib
#[cold] и likelyКомпилятор Rust (LLVM) очень умный, но иногда мы знаем о поведении программы больше, чем он. Мы можем подсказать ему, какая ветка кода будет выполняться чаще.
1. Атрибут
#[cold]Идеально для обработки ошибок. Вы говорите компилятору: "Сюда мы будем заходить крайне редко".
Компилятор сдвинет этот код в "дальний угол" бинарника, чтобы "горячий" путь шел линейно в памяти (это улучшает работу кэша инструкций и BPU).
#[cold] // <-- Подсказка
fn handle_fatal_error() {
// Логирование, паника, сложная логика...
}
fn process(data: &str) {
if let Err(_) = parse(data) {
handle_fatal_error(); // Компилятор знает, что это маловероятно
return;
}
// Happy path идет дальше линейно
}
2.
likely / unlikely (Nightly / Crates)В стабильном Rust пока нет
std::intrinsics::likely, но есть крейт likely или хаки. Это прямая инструкция процессору через LLVM.
// С крейтом `likely`
if likely(x > 0) {
// Оптимизатор будет строить код так,
// будто это условие почти всегда true
}
Используйте
#[cold] для ошибок и граничных случаев. Это стабильно, бесплатно и делает код чище.#rust #optimization #llvm #hints
👉 @rust_lib
👍16✍2🥰2❤1🔥1🤗1
🚄 Struct of Arrays (SoA): Пишем для железа
В прошлом посте мы поняли, что массив жирных структур (
Решение из Data-Oriented Design - вывернуть структуру наизнанку. Это называется Struct of Arrays (SoA).
Вместо массива структур, мы делаем структуру массивов:
Теперь, чтобы обновить позиции, мы пишем:
Почему процессор плачет от счастья?
1. Идеальный кэш: Когда процессор берет 64 байта из
2. Hardware Prefetcher: У процессора есть умный блок, который видит: "Ага, он читает память линейно!". И он начинает предзагружать следующие позиции в кэш до того, как они понадобятся циклу.
3. SIMD (Автовекторизация): Компилятору в 100 раз проще применить те самые AVX-инструкции к плоскому однородному массиву.
SoA может ускорить математические циклы в 5-10 раз без изменения алгоритма! Но есть минус: удалять или добавлять одну частицу теперь неудобно (надо менять 4 разных вектора).
#rust #dop #soa #memory #optimization
👉 @rust_lib
В прошлом посте мы поняли, что массив жирных структур (
Vec<Particle>) забивает кэш процессора ненужными данными. Решение из Data-Oriented Design - вывернуть структуру наизнанку. Это называется Struct of Arrays (SoA).
Вместо массива структур, мы делаем структуру массивов:
struct Particles {
positions: Vec<[f32; 3]>,
velocities: Vec<[f32; 3]>,
colors: Vec<[u8; 4]>,
lifetimes: Vec<f32>,
}
Теперь, чтобы обновить позиции, мы пишем:
for i in 0..count {
positions[i][0] += velocities[i][0];
positions[i][1] += velocities[i][1];
positions[i][2] += velocities[i][2];
}
Почему процессор плачет от счастья?
1. Идеальный кэш: Когда процессор берет 64 байта из
positions, он получает ровно позиции. Никаких имен или цветов. Ни байта не тратится впустую.2. Hardware Prefetcher: У процессора есть умный блок, который видит: "Ага, он читает память линейно!". И он начинает предзагружать следующие позиции в кэш до того, как они понадобятся циклу.
3. SIMD (Автовекторизация): Компилятору в 100 раз проще применить те самые AVX-инструкции к плоскому однородному массиву.
SoA может ускорить математические циклы в 5-10 раз без изменения алгоритма! Но есть минус: удалять или добавлять одну частицу теперь неудобно (надо менять 4 разных вектора).
#rust #dop #soa #memory #optimization
👉 @rust_lib
👍12🔥5❤1🥰1🤔1