مقدمة
تُعرف Rust بسرعتها وأمانها، ومع ذلك، حتى في هذه اللغة يمكن كتابة كود غير مثالي. تحسين أداء تطبيقات Rust هو فن يجمع بين فهم الآليات منخفضة المستوى للغة، والعمل مع الذاكرة، والاستخدام الفعال للتوازي. في هذه المقالة، سنقوم بتحليل التقنيات الرئيسية التي ستساعدك على استخراج أقصى استفادة من كودك. ستتعلم كيفية إنشاء ملفات تعريف للتطبيقات، واستخدام zero-cost abstractions، وإدارة الذاكرة بدون جامع القمامة، وتطبيق تعدد الخيوط بشكل صحيح.
1. إنشاء ملفات التعريف (Profiling) واختبارات الأداء (Benchmarking)
قبل التحسين، من الضروري فهم ما الذي يسبب البطء تحديدًا. توفر Rust أدوات ممتازة لقياس الأداء.
1.1. اختبارات الأداء المدمجة (cargo bench)
استخدم cargo bench لكتابة اختبارات أداء مصغرة. للقيام بذلك، أضف إلى Cargo.toml:
[dev-dependencies]criterion = { version = "0.5", features = ["html_reports"] }مثال على اختبار أداء:
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn fibonacci(n: u64) -> u64 { match n { 0 => 1, 1 => 1, n => fibonacci(n-1) + fibonacci(n-2), }}
fn bench_fib(c: &mut Criterion) { c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));}
criterion_group!(benches, bench_fib);criterion_main!(benches);1.2. إنشاء ملفات التعريف باستخدام perf و flamegraph
لأجزاء الكود الساخنة، استخدم perf (لنظام Linux) أو flamegraph. قم بالتثبيت:
cargo install flamegraphقم بتشغيل إنشاء ملف التعريف:
cargo flamegraph --bin my_appسيؤدي هذا إلى إنشاء ملف SVG يحتوي على flamegraph، حيث تظهر الدوال الأكثر استهلاكًا للموارد فورًا.
2. تحسين العمل مع الذاكرة
لا تحتوي Rust على جامع قمامة، لكن هذا لا يعني أنه يمكن إدارة الذاكرة بإهمال. النقاط الرئيسية:
2.1. تجنب التخصيصات غير الضرورية
كل استدعاء لـ Box::new() أو Vec::push() قد يكون مكلفًا. استخدم Vec::with_capacity() للتخصيص المسبق للذاكرة:
// سيءlet mut v = Vec::new();for i in 0..1000 { v.push(i);}
// جيدlet mut v = Vec::with_capacity(1000);for i in 0..1000 { v.push(i);}2.2. استخدم المكدس (Stack) بدلاً من الكومة (Heap)
إذا كان حجم البيانات معروفًا في وقت الترجمة، قم بتخزينها على المكدس. على سبيل المثال، استخدم المصفوفات بدلاً من Vec:
// على المكدسlet arr: [i32; 5] = [1, 2, 3, 4, 5];
// في الكومة (تخصيص غير ضروري)let vec = vec![1, 2, 3, 4, 5];2.3. الاستنساخ (Cloning) مقابل الاقتراض (Borrowing)
تجنب استخدام clone() المكلف. استخدم المراجع والاقتراض:
fn process(data: &[u8]) { /* ... */ }
// سيء: نسخ البياناتlet data = vec![1, 2, 3];process(&data.clone());
// جيد: تمرير مرجعprocess(&data);3. Zero-cost abstractions والمكررات (Iterators)
تشتهر Rust بأن الهياكل عالية المستوى تُترجم إلى كود آلة فعال. ومع ذلك، من المهم معرفة كيفية استخدامها بشكل صحيح.
3.1. المكررات سريعة
غالبًا ما تعمل سلاسل المكررات بشكل أسرع من الحلقات اليدوية، حيث يمكن للمُحسّن (Compiler) تحسينها (loop unrolling, inlining):
// حلقة يدويةlet mut sum = 0;for i in 0..1000 { sum += i * 2;}
// مكرر (غالبًا أسرع)let sum: i32 = (0..1000).map(|x| x * 2).sum();3.2. تجنب الإرسال الديناميكي (Dynamic Dispatch)
استخدام dyn Trait يضيف تكاليف إضافية للاستدعاءات الافتراضية. كلما أمكن، استخدم الإرسال الثابت (Static Dispatch) من خلال التعميمات (impl Trait):
// إرسال ديناميكي (أبطأ)fn process(d: &dyn SomeTrait) { d.do_something(