-1

Tối Ưu Hiệu Năng Rust: Từ Rối Não Đến "Đỉnh Cao"

Ai cũng biết Rust nhanh, an toàn, đa luồng ngon. Nhưng thực tế, nhiều người viết Rust vẫn "gáy" xong chạy benchmark lại thấy như dính phanh. Vì sao? Rust trao cho bạn siêu xe Ferrari, nhưng cách chạy vẫn như đi chợ! Hiệu năng không phải bí ẩn – nó là khoa học (và vài "mẹo nhỏ" đúng lối).

image.png

Trước khi nói đến tối ưu, hãy tiết kiệm thời gian setup. Đừng tốn nửa ngày mò cài Rust, PostgreSQL, Redis... Hãy để ServBay lo giúp bạn: chỉ một click là có Rust dev environment với đủ database – chuẩn bị sẵn sàng, vào việc là phi luôn.

image.png


Mẹo 1: Ưu Tiên &str Thay Vì String Khi Nhận Tham Số

Rất nhiều bạn mới cứ thấy chuỗi là dùng String.

Không nên:

fn welcome_user(name: String) {
println!("Hello, {}! Chào mừng đến với Rust!", name);
}

fn main() {
let user_name = "CodeWizard".to_string();
welcome_user(user_name.clone());
println!("Tên tài khoản là: {}", user_name); // Nếu không clone, sẽ bị lỗi ownership!
}

Làm đúng:

fn welcome_user(name: &str) {
println!("Hello, {}! Chào mừng đến với Rust!", name);
}

fn main() {
let user_name = "CodeWizard".to_string();
welcome_user(&user_name);
welcome_user("Newbie");
println!("Tên tài khoản là: {}", user_name);
}

Vì sao? Dùng String sẽ bị chuyển quyền sở hữu hoặc clone tốn bộ nhớ – còn &str chỉ là tham chiếu, nhẹ như lông hồng!


Mẹo 2: Share Dữ Liệu Bằng Arc, Đừng Lạm Dụng .clone()

Nhiều thread chia sẻ chung dữ liệu lớn? .clone() tạo ra nhiều bản, phí tài nguyên lắm!

Không đúng:

use std::thread;

#[derive(Clone)]
struct AppConfig {
api_key: String,
timeout: u32,
}

fn main() {
let config = AppConfig { api_key: "key".to_string(), timeout: 5000 };
let mut handles = vec![];
for i in 0..5 {
let thread_config = config.clone();
handles.push(thread::spawn(move || {
println!("Thread {}: {}", i, thread_config.api_key);
}));
}
for handle in handles { handle.join().unwrap(); }
}

Nên dùng:

use std::sync::Arc;
use std::thread;

struct AppConfig {
api_key: String,
timeout: u32,
}

fn main() {
let config = Arc::new(AppConfig { api_key: "key".to_string(), timeout: 5000 });
let mut handles = vec![];
for i in 0..5 {
let thread_config = Arc::clone(&config);
handles.push(thread::spawn(move || {
println!("Thread {}: {}", i, thread_config.api_key);
}));
}
for handle in handles { handle.join().unwrap(); }
}

Vì sao? Arc chỉ tăng bộ đếm tham chiếu, cực kỳ nhẹ nhàng!


Mẹo 3: Luôn Ưu Tiên Iterator, Đừng Cứ For i in 0..len

Vẫn còn dùng for i in 0..vec.len()? Hạn chế hiệu năng, code lại rối!

Không tối ưu:

fn main() {
let numbers = vec!;​
let mut sum = 0;
for i in 0..numbers.len() {
if numbers[i] % 2 == 0 {
sum += numbers[i] * numbers[i];
}
}
println!("{}", sum);
}

Tối ưu:

fn main() {
let numbers = vec!;​
let sum: i32 = numbers
.iter()
.filter(|&&n| n % 2 == 0)
.map(|&n| n * n)
.sum();
println!("{}", sum);
}

Vì sao? Iterator “zero-cost abstraction” – vừa sạch lại vừa cực nhanh (compilier optimize luôn)!


Mẹo 4: Ưu Tiên Generic Thay Vì Box<dyn Trait>

Xử lý đa hình, compile-time generic luôn hiệu quả hơn dynamic dispatch!

Dynamic dispatch (kém tối ưu): ``` trait Sound { fn make_sound(&self) -> String; } struct Dog; impl Sound for Dog { fn make_sound(&self) -> String { "Gâu gâu!".to_string() } } struct Cat; impl Sound for Cat { fn make_sound(&self) -> String { "Meo~".to_string() } }

fn trigger_sound(animal: Box<dyn Sound>) { println!("{}", animal.make_sound()); }

fn main() { trigger_sound(Box::new(Dog)); trigger_sound(Box::new(Cat)); }



**Static dispatch (tối ưu):**
    
    ```
trait Sound { fn make_sound(&self) -> String; }
struct Dog; impl Sound for Dog { fn make_sound(&self) -> String { "Gâu gâu!".to_string() } }
struct Cat; impl Sound for Cat { fn make_sound(&self) -> String { "Meo~".to_string() } }

fn trigger_sound<T: Sound>(animal: T) {
println!("{}", animal.make_sound());
}

fn main() {
trigger_sound(Dog);
trigger_sound(Cat);
}

Vì sao? Generic cho phép compile-time resolve, không tốn lookup bảng vtable khi chạy.


Mẹo 5: #[inline] Cho Hàm Nhỏ, Hot

Các hàm nhỏ gọi nhiều lần, inline luôn để xóa bỏ chi phí call.

```

#[inline] fn is_positive(n: i32) -> bool { n > 0 }

fn count_positives(numbers: &[i32]) -> usize { numbers.iter().filter(|&&n| is_positive(n)).count() }



---

## Mẹo 6: Ưu Tiên Alloc Trên Stack

Khai báo trên stack siêu nhanh hơn heap rất nhiều!

**Heap:**
    ```
struct Point { x: f64, y: f64 }
fn main() {
let p1 = Box::new(Point { x: 1.0, y: 2.0 });
println!("Heap point: ({}, {})", p1.x, p1.y);
}

Stack: struct Point { x: f64, y: f64 } fn main() { let p1 = Point { x: 1.0, y: 2.0 }; println!("Stack point: ({}, {})", p1.x, p1.y); }


Mẹo 7: Tạo Bộ Nhớ Lớn Siêu Nhanh Với MaybeUninit

Cần cấp phát bộ nhớ lớn mà chắc chắn sẽ tự fill toàn bộ? Tránh init zero mặc định nhé.

use std::mem::MaybeUninit;

const BUFFER_SIZE: usize = 1024 * 1024;

fn main() {
let mut buffer: Vec<MaybeUninit<u8>> = Vec::with_capacity(BUFFER_SIZE);
unsafe {
buffer.set_len(BUFFER_SIZE);
for i in 0..BUFFER_SIZE {
*buffer.get_mut_unchecked(i) = MaybeUninit::new((i % 256) as u8);
}
}
let buffer: Vec<u8> = unsafe { std::mem::transmute(buffer) };
println!("First byte: {}, last: {}", buffer, buffer[BUFFER_SIZE - 1]);
}

Lưu ý: Đây là kỹ thuật nâng cao (unsafe), chỉ dùng khi bạn hiểu rõ!


Tóm lại

  • Hạn chế clone/copy: ưu tiên borrow, smart pointer như Arc.
  • Tận dụng sức mạnh của compiler: iterator, generic.
  • Biết rõ dữ liệu nào trên stack, dữ liệu nào trên heap.
  • Chỉ tối ưu khi đo đạc thực sự cần thiết (dùng công cụ profile trước!).

Và đừng quên: một môi trường dev ngon giúp bạn auto tối ưu hơn nửa chặng đường. Có ServBay khỏi lo toolchain, chỉ tập trung tinh chỉnh code!
Bắt đầu với Rust dev environment – biến chiếc xe mua rau thành siêu xe hiệu năng.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí