Rust
2.23K subscribers
169 photos
106 videos
2 files
233 links
Полезный контент по программированию на Rust
Download Telegram
🏎️ SIMD для ленивых: Автовекторизация в Rust

SIMD - это когда процессор одной инструкцией складывает не два числа, а сразу 4, 8 или 16.

Многие думают, что SIMD это сложно и требует unsafe. Но лучший SIMD тот, который LLVM написал за вас.

Допустим, у вас есть цикл:


pub fn add_arrays(a: &[f32], b: &[f32], out: &mut [f32]) {
// Простые смертные пишут так:
for i in 0..a.len() {
out[i] = a[i] + b[i];
}
}



Если вы скомпилируете это в релизе для x86_64, компилятор сгенерирует инструкции SSE2 (обрабатывая по 4 числа f32 за раз). Почему? Потому что SSE2 гарантированно есть на всех 64-битных процессорах Intel/AMD.

Как сделать еще быстрее?
Если вы знаете, что ваш код будет работать на современном железе, разрешите компилятору использовать AVX2 (по 8 чисел f32).


RUSTFLAGS="-C target-cpu=native" cargo build --release



Но есть нюанс: Компилятор очень осторожен. Он не применит SIMD, если:

1. Есть риск паники. Если срезы разной длины, компилятор вставит проверки границ (bounds check) внутри цикла. Это убивает векторизацию.
Лечение: Используйте итераторы (zip) или явно проверяйте длины до цикла и используйте assert!, чтобы дать хинт оптимизатору.


2. Есть риск алиасинга. Если out пересекается в памяти с a или b.
Лечение: Rust тут молодец, его правила заимствования гарантируют отсутствие мутабельного алиасинга.

Всегда смотрите в ассемблер (cargo-show-asm), чтобы убедиться, что цикл развернулся в SIMD-инструкции (ищите что-то вроде vaddps вместо addss).

#rust #simd #performance #llvm #autovectorization

👉 @rust_lib
👍148🥰1🤗1
💀 Хардкорный режим: std::arch и интринсики

Автовекторизация не сработала? Алгоритм слишком сложен для LLVM? Придется марать руки.

Встречайте std::arch. Это модуль, дающий доступ к конкретным инструкциям процессора (интринсикам).
Это очень unsafe.
Это не портабельно (код для x86 не заработает на ARM M1).

Как выглядит сложение массивов на AVX2 (x86_64):


#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;

// Атрибут важен! Он говорит компилятору, что внутри этой функции
// можно использовать 256-битные регистры.
#[target_feature(enable = "avx2")]
unsafe fn add_avx2(a: &[f32], b: &[f32], out: &mut [f32]) {
// Мы обязаны обрабатывать данные чанками по 8 штук.
// (Опустим проверки длин и обработку "хвостов" для краткости)
for i in (0..a.len()).step_by(8) {
// Загружаем 8 float'ов в 256-битный регистр
let vec_a = _mm256_loadu_ps(a.as_ptr().add(i));
let vec_b = _mm256_loadu_ps(b.as_ptr().add(i));

// ОДНА инструкция сложения для 8 пар чисел
let vec_res = _mm256_add_ps(vec_a, vec_b);

// Выгружаем обратно в память
_mm256_storeu_ps(out.as_mut_ptr().add(i), vec_res);
}
}



Главная опасность: Если вы вызовете эту функцию на старом процессоре (без AVX2), программа упадет с ошибкой "Illegal Instruction".

Решение: Runtime detection. Вы должны проверять фичи процессора в рантайме.


fn add_smart(a: &[f32], b: &[f32], out: &mut [f32]) {
if is_x86_feature_detected!("avx2") {
// Безопасно, мы проверили
unsafe { add_avx2(a, b, out) };
} else {
// Медленный фоллбэк для старого железа
add_scalar(a, b, out);
}
}



Это сложно, больно, но дает максимальный контроль над железом.

#rust #simd #unsafe #lowlevel #intrinsics

👉 @rust_lib
👍9🔥3🙈21🥰1🤗1
🚀 Portable SIMD: Пишем один раз, работает везде

В прошлом посте вы видели ужас ручных интринсиков. Представьте, что вам нужно поддерживать SSE2, AVX2, AVX-512, да еще и ARM NEON для макбуков. Это ад из cfg_if и дублирования кода.

Rust сообщество решает эту проблему. Встречайте Portable SIMD (сейчас это крейт portable-simd или фича #![feature(portable_simd)] в nightly).

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

Выглядит это потрясающе чисто:


#![feature(portable_simd)] // Нужен nightly Rust
use std::simd::{f32x8, Simd};

fn add_portable(a: &[f32], b: &[f32], out: &mut [f32]) {
// Берем чанки по 8 элементов
let chunks_a = a.chunks_exact(8);
let chunks_b = b.chunks_exact(8);
let chunks_out = out.chunks_exact_mut(8);

for ((slice_a, slice_b), slice_out) in chunks_a.zip(chunks_b).zip(chunks_out) {
// Превращаем слайсы в SIMD-вектора (почти бесплатно)
let vec_a = f32x8::from_slice(slice_a);
let vec_b = f32x8::from_slice(slice_b);

// Обычный оператор "+" работает как SIMD сложение!
let vec_res = vec_a + vec_b;

// Записываем обратно
vec_res.copy_to_slice(slice_out);
}
// + нужно обработать "хвост", если длина не кратна 8
}



Никакого unsafe! Никаких _mm256_ugly_names!
Если вы компилируете это под AVX2 - будет AVX2. Под ARM NEON - будет NEON.

Это будущее высокопроизводительного Rust. Ждем стабилизации.

#rust #simd #nightly #future

👉 @rust_lib
🔥222👍1🥰1🤗1