مقدمة: لماذا Rust سريع بالفعل ولكنه ليس مثاليًا
يتم وضع Rust تقليديًا كلغة توفر أداء C++ مع أمان الذاكرة. ومع ذلك، فإن "سريع افتراضيًا" هو خرافة. حتى في Rust يمكنك كتابة كود بطيء: الاستنساخ المفرط، هياكل البيانات غير المثلى، الاستخدام غير الصحيح للتخصيصات. في هذه المقالة، سنناقش الطرق العملية التي ستساعدك على استخراج أقصى استفادة من تطبيق Rust الخاص بك دون التضحية بالأمان.
سننظر في أربعة جوانب رئيسية: التنميط، التجريدات ذات التكلفة الصفرية، إدارة الذاكرة والتوازي. يحتوي كل قسم على أمثلة كود حقيقية ونتائج قابلة للقياس.
1. التنميط: لا تخمن، بل قس
بدون التنميط، أي تحسين هو إطلاق نار في الظلام. في Rust، هناك عدة أدوات قوية.
1.1. اختبارات الأداء المدمجة مع cargo bench
للتحسينات الدقيقة، استخدم #![feature(test)] ووحدة test. مثال:
#![feature(test)]extern crate test;
pub fn slow_sum(data: &[u64]) -> u64 { data.iter().sum()}
#[cfg(test)]mod benchmarks { use super::*; use test::Bencher;
#[bench] fn bench_slow_sum(b: &mut Bencher) { let data: Vec<u64> = (0..1000).collect(); b.iter(|| slow_sum(&data)); }}التشغيل: cargo bench. سترى وقت التنفيذ الدقيق وعدد التكرارات.
1.2. أدوات التنميط الخارجية: perf وflamegraph
للبحث عن الاختناقات في التطبيقات الكبيرة، استخدم perf في Linux أو DTrace في macOS. قم بتثبيت أداة التصور:
cargo install flamegraphcargo flamegraph --bin your_appسينشئ هذا رسمًا بيانيًا SVG، حيث كل "لسان لهب" هو مكدس استدعاء. كلما كانت الكتلة أوسع، زاد الوقت المستغرق في هذه الدالة.
نصيحة: قم دائمًا بالتنميط في وضع الإصدار: cargo build --release. وضع التصحيح يضيف فحوصات الحدود ومعلومات التصحيح، مما يشوه النتائج.
2. التجريدات ذات التكلفة الصفرية: استخدمها، ولكن بحكمة
تفتخر Rust بأن التجريدات عالية المستوى لا تضيف تكاليف إضافية. لكن هذا يعمل فقط مع الاستخدام الصحيح.
2.1. المكررات مقابل الحلقات
يتم تجميع المكررات في Rust إلى نفس كود الآلة الذي تنتجه الحلقات اليدوية. مثال:
// سيء: حلقة يدوية مع فهارسfn manual_sum(data: &[u64]) -> u64 { let mut sum = 0; for i in 0..data.len() { sum += data[i]; } sum}
// جيد: مكرر (بتكلفة صفرية)fn iter_sum(data: &[u64]) -> u64 { data.iter().sum()}تظهر اختبارات الأداء أداءً متطابقًا. لكن المكررات أكثر أمانًا (لا حاجة لفحص الحدود يدويًا) وأكثر قابلية للقراءة.
2.2. تجنب التخصيصات غير الضرورية
كل استدعاء لـ Vec::new() أو String::new() هو تخصيص في الكومة. استخدم المصفوفات المكدسية حيثما أمكن:
// سيء: تخصيص لكل استدعاءfn process_items(items: &[u32]) -> Vec<u32> { items.iter().map(|x| x * 2).collect()}
// جيد: إعادة استخدام المخزن المؤقتfn process_items_inplace(items: &mut [u32]) { for item in items.iter_mut() { *item *= 2; }}إذا كان التخصيص لا مفر منه، استخدم Vec::with_capacity(n) لتجنب عمليات إعادة التخصيص المتعددة.
3. إدارة الذاكرة: الملكية والاستنساخ
نظام الملكية في Rust هو الأداة الرئيسية للتحسين. يسمح بتجنب جمع القمامة، لكنه يتطلب نهجًا واعيًا.
3.1. الاقتراض مقابل الاستنساخ
كل .clone() هو نسخ للبيانات. حاول استخدام المراجع:
// سيء: استنساخ السلسلة بأكملهاfn greet(name: String) -> String { format!("Hello, {