لماذا يتطلب Rust نهجًا خاصًا لاختبارات الأداء؟
يشتهر Rust بسرعته، ولكن حتى أسرع لغة يمكن إبطائها بتنفيذ غير مناسب. هل سبق لك أن كتبت خوارزمية في Rust معتقدًا: "هذا سيكون سريعًا كالبرق!"، ثم قمت بتشغيلها ولم تفهم أين ضاعت الميكروثانية؟ مألوف؟
اختبارات الأداء في Rust ليست مجرد تشغيل مؤقت وقياس الوقت. إنها علم كامل: يجب مراعاة تحسينات المترجم، وتخزين المعالج المؤقت، وحتى كيفية إدارة Rust للذاكرة. في هذه المقالة، سنشرح كيفية إجراء اختبارات الأداء الصحيحة لبرامج Rust بحيث تحصل على أرقام دقيقة وليست نتائج عشوائية.
سننتقل من القياسات البسيطة باستخدام std::time إلى الأداة الاحترافية criterion المستخدمة في المشاريع الحقيقية. هيا بنا!
1. أبسط طريقة: std::time::Instant
إذا كنت بحاجة إلى "اختبار" سريع لأداء جزء من الكود - دون تثبيت أي crate، فقط أدخل القياس مباشرة في main. ولكن هناك مشكلة: قد يقوم محسن Rust "بإزالة" الكود الخاص بك إذا لم يستخدم النتيجة.
use std::time::Instant;
fn main() { let start = Instant::now();
// الكود الذي تريد قياسه let mut sum = 0u64; for i in 0..1_000_000 { sum += i; }
let duration = start.elapsed(); println!("وقت التنفيذ: {:?}", duration); // تأكد من استخدام النتيجة! println!("المجموع: {}", sum);}لماذا لا يمكنك قياس تشغيل واحد فقط؟
تشغيل واحد هو يانصيب. قد يكون النظام مشغولاً، أو المعالج يخفض سرعته، أو ذاكرة التخزين المؤقت باردة. اختبار الأداء الصحيح في Rust يجب أن:
- يسخن الكود (warm-up) - عدة تكرارات قبل القياس
- يشغل الكود عدة مرات (آلاف التكرارات)
- يحسب متوسط النتائج
التنفيذ اليدوي لهذا هو طريق للأخطاء. لذلك ابتكر مجتمع Rust criterion.
2. اختبارات الأداء الاحترافية: crate Criterion
Criterion.rs هو المعيار الفعلي لاختبارات الأداء في Rust. يُستخدم في المترجم rustc نفسه وفي مئات الـ crates. لماذا هو رائع؟
- تسخين تلقائي ومعالجة إحصائية
- رسوم بيانية ومقارنة مع التشغيلات السابقة
- كشف الانحدارات (إذا أصبح الكود أبطأ - ستعرف)
- دعم اختبارات الأداء المعلمية
التثبيت والإعداد
أضف إلى Cargo.toml:
[dev-dependencies]criterion = { version = "0.5", features = ["html_reports"] }
[[bench]]name = "my_benchmark"harness = falseأنشئ ملف benches/my_benchmark.rs:
use criterion::{black_box, criterion_group, criterion_main, Criterion};
// الدالة التي نختبر أداءهاfn fibonacci(n: u64) -> u64 { match n { 0 => 0, 1 => 1, _ => fibonacci(n - 1) + fibonacci(n - 2), }}
fn bench_fibonacci(c: &mut Criterion) { c.bench_function("fibonacci 20", |b| { // black_box - يمنع المحسن من إزالة الكود b.iter(|| fibonacci(black_box(20))) });}
criterion_group!(benches, bench_fibonacci);criterion_main!(benches);التشغيل: cargo bench. سترى شيئًا مثل:
fibonacci 20 time: [24.567 µs 24.789 µs 25.012 µs]ثلاثة أرقام هي فاصل الثقة (الحد الأدنى، المتوسط، الحد الأعلى). إذا كان الفاصل ضيقًا - فإن اختبار الأداء مستقر.
3. المكون السري: black_box
ربما لاحظت black_box في المثال. هذه دالة سحرية من criterion، تمنع LLVM من تحسين الوسائط أو النتيجة. بدونها، قد يُظهر اختبار الأداء الخاص بك 0 نانوثانية - لأن المترجم قام ببساطة بحساب الثابت في وقت الترجمة.
مثال ص