مقدمة: لماذا أداء Rust ليس سحرًا بل انضباط
تُعتبر Rust بحق واحدة من أسرع لغات البرمجة النظامية. ومع ذلك، كما هو الحال في أي لغة أخرى، فإن الكود "الخام" لا يعمل دائمًا بأقصى إمكانياته. العديد من المطورين المبتدئين، عند انتقالهم إلى Rust من Python أو JavaScript، يتوقعون زيادة فورية في السرعة، لكنهم ينسون أن التحسين هو عملية وليس هدية من المترجم.
هذه المقالة مخصصة لأولئك الذين كتبوا بالفعل برامجهم الأولى بلغة Rust ويريدون فهم كيفية استخراج أقصى استفادة منها. سنناقش ثلاثة اتجاهات رئيسية: التوصيف (لمعرفة ما يجب إصلاحه)، التجريدات ذات التكلفة الصفرية (حتى لا تدفع ثمن ما لا تستخدمه)، وإدارة الذاكرة (لتجنب الاختناقات). جميع الأمثلة ستكون مصحوبة بكود يمكن تشغيله فورًا في بيئتك.
1. التوصيف: قبل أن تحسّن، قِس
القاعدة الأساسية للتحسين: لا تحسّن بشكل أعمى. بدون توصيف، أنت تخاطر بإضاعة ساعات في "تسريع" كود يعمل بالفعل بسرعة، بينما تبقى نقطة الاختناق الحقيقية غير مكتشفة.
أدوات التوصيف
في نظام Rust البيئي، هناك العديد من الأدوات الممتازة. للبداية، أوصي باستخدام cargo-flamegraph — فهو يُصور أين يقضي البرنامج معظم الوقت. التثبيت:
cargo install flamegraphتشغيل التوصيف لملفك الثنائي:
cargo flamegraph --bin my_appبعد التنفيذ، ستحصل على ملف SVG يحتوي على "مخطط اللهب". الأعمدة العريضة تُظهر الدوال التي تستهلك أكبر قدر من وحدة المعالجة المركزية.
مثال: نجد نقطة الاختناق
لنفترض أن لدينا دالة تجمع مربعات الأعداد:
fn sum_of_squares(n: u64) -> u64 { let mut sum = 0; for i in 1..=n { sum += i * i; } sum}إذا أظهر أداة التوصيف أن 90% من الوقت يُستهلك في هذه الدالة، فإننا نعرف أين ننظر. أما إذا كانت نقطة الاختناق هي الإدخال/الإخراج (مثل قراءة ملف)، فإن تحسين العمليات الحسابية لن يعطي نتيجة.
2. التجريدات ذات التكلفة الصفرية: كيف يساعد المترجم (ويعيق)
واحدة من أهم ميزات Rust هي مفهوم التجريدات ذات التكلفة الصفرية. هذا يعني أن البنى عالية المستوى (المكررات، closures، السمات) يجب ألا تضيف تكاليف إضافية مقارنة بكتابة الحلقات يدويًا. ومع ذلك، هذا يعمل فقط عند الاستخدام الصحيح.
المكررات مقابل الحلقات
يكتب العديد من المبتدئين بهذه الطريقة:
let v: Vec<i32> = (0..1000).collect();let mut result = Vec::new();for i in &v { result.push(i * 2);}يمكن إعادة كتابة هذا الكود باستخدام المكررات، وسيقوم مترجم Rust بتحسينه إلى نفس كود الآلة:
let v: Vec<i32> = (0..1000).collect();let result: Vec<i32> = v.iter().map(|x| x * 2).collect();علاوة على ذلك، إذا استخدمت filter وmap وfold بشكل متسلسل، يمكن للمترجم "دمجها" في مسار واحد — وهذا يُسمى loop fusion. الحلقات اليدوية لا تقدم هذا التحسين.
Box<dyn Trait> مقابل الأدوية (Generics)
خطأ شائع آخر هو الإفراط في استخدام كائنات السمات (Box<dyn SomeTrait>). فهي تسبب إرسالًا ديناميكيًا، مما يضيف تكاليف إضافية. إذا كان عدد الخيارات معروفًا في وقت الترجمة، استخدم الأدوية (Generics) (الإرسال الثابت):
// سيء: إرسال ديناميكيfn process(items: Vec<Box<dyn Processor>>) { for item in items { item.run(); }}
// جيد: إرسال ثابتfn process<T: Processor>(items: Vec<T>) { for item in items { item.run()