لماذا Rust سريع، ولكن ليس دائمًا؟
\nRust هي لغة تعد بأداء C++ مع أمان الذاكرة. ولكن حتى في Rust يمكنك كتابة كود بطيء. قراران خاطئان — وستبدأ خدمتك في التباطؤ تحت الحمل. الخبر الجيد: Rust يمنحك جميع الأدوات لتحديد وإزالة نقاط الاختناق. في هذه المقالة، سنشرح كيفية تنميط كود Rust وأي تقنيات التحسين تعمل حقًا.
\n\nلن نتحدث بشكل نظري. سترى أمثلة حقيقية للكود، وستتعلم كيفية استخدام perf وflamegraph وvalgrind والمقاييس المدمجة. بعد القراءة، ستتمكن من إجراء تدقيق أداء لمشروعك بنفسك.
مجموعة أدوات تنميط Rust
\nقبل تحسين أي شيء، تحتاج إلى فهم ما الذي يبطئ الأمور. إليك ثلاث أدوات عملية ستساعدك على النظر داخل البرنامج.
\n\n1. المقاييس باستخدام cargo bench
\nالطريقة القياسية لقياس الأداء هي كتابة معيار أداء. في Rust، يتم استخدام crate criterion أو إطار الاختبار المدمج. مثال:
use criterion::{black_box, criterion_group, criterion_main, Criterion};\n\nfn fibonacci(n: u64) -> u64 {\n match n {\n 0 => 0,\n 1 => 1,\n _ => fibonacci(n - 1) + fibonacci(n - 2),\n }\n}\n\nfn bench_fib(c: &mut Criterion) {\n c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));\n}\n\ncriterion_group!(benches, bench_fib);\ncriterion_main!(benches);\n\n\nالتشغيل: cargo bench. ستحصل على وقت التنفيذ والانحراف والمقارنة مع عمليات التشغيل السابقة. هذه هي القاعدة التي يجب أن تبدأ بها أي تحسين.
2. التنميط باستخدام perf وflamegraph
\nعندما تحتاج إلى معرفة أي الوظائف تستهلك المعالج، استخدم perf. اجمع البيانات، ثم قم بتصورها على شكل مخطط لهب:
# جمع البيانات\nperf record -g ./target/release/my_app\n# إنشاء flamegraph\nperf script | stackcollapse-perf.pl | flamegraph.pl > profile.svg\n\n\nسيظهر لك مخطط اللهب الأجزاء "الساخنة" من الكود. كلما كانت الكتلة أوسع، زاد الوقت المستغرق في هذه الوظيفة. هذه هي الطريقة الأكثر وضوحًا للعثور على نقطة الاختناق.
\n\n3. البحث عن تسرب الذاكرة باستخدام Valgrind
\nRust يحمي من تسرب الذاكرة على مستوى الترجمة، ولكن ليس من جميعها. على سبيل المثال، إذا كنت تستخدم Rc أو Arc مع مراجع دائرية، فقد لا يتم تحرير الذاكرة. سيساعدك Valgrind في اكتشاف ذلك:
valgrind --tool=massif ./target/debug/my_app\n\n\nسيظهر Massif مقدار الذاكرة التي يستهلكها البرنامج في كل لحظة. إذا لم ينخفض الرسم البياني بعد انتهاء العمل — فابحث عن تسرب.
\n\nتقنيات عملية لتحسين كود Rust
\nالآن بعد أن عرفت ما تبحث عنه، دعنا نشرح كيفية إصلاحه. إليك خمس طرق مثبتة تعمل حقًا على تسريع الكود.
\n\n1. التجريدات ذات التكلفة الصفرية: استخدم التكرارات بشكل صحيح
\nيتم ترجمة التكرارات في Rust إلى كود بنفس كفاءة الحلقات اليدوية. ولكن فقط إذا لم تقم بعمل نسخ غير ضرورية. قارن:
\n\n// سيء: إنشاء Vec، نسخ، تصفية\nlet result: Vec<u32> = data.iter().filter(|x| x % 2 == 0).collect();\n// جيد: العمل مع المكرر بدون مجموعات وسيطة\nlet sum: u32 = data.iter().filter(|x| x % 2 == 0).sum();\n\n\nالخيار الثاني لا يخصص ذاكرة لمتجه مؤقت. إذا كنت تحتاج فقط إلى قيمة واحدة — لا تجمع.
\n\n2. التخزين المؤقت للنتائج
\nإذا تم استدعاء دالة بشكل متكرر بنفس الوسائط، قم بتخزين النتيجة مؤقتًا. استخدم HashMap أو crate lru للحد من حجم ذاكرة التخزين المؤقت:
use std::collections::HashMap;\n\nfn expensive_calc(x: u64) -> u64 {\n // ... حسابات طويلة\n x * x\n}\n\nfn c