مكتبة Rust مع تطبيق: تحسين الأداء والتعبئة | PythonLib

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

حزمة واحدة، كيانان: حقيقة أم خيال؟

\n\n

تخيل أنك كتبت مكتبة رائعة بلغة Rust لمعالجة البيانات الضخمة. إنها سريعة وآمنة وأنيقة. لكنك لا تحتاج فقط إلى API للمطورين الآخرين، بل تحتاج أيضًا إلى أداة CLI جاهزة لاختبار المكتبة أو استخدامها في خطوط الأنابيب. يظهر السؤال الكلاسيكي الذي حصد آلاف المشاهدات على Stack Overflow: «Package with both a library and a binary?» (حزمة تحتوي على مكتبة وتطبيق).

\n\n

للمبتدئ في Rust، يبدو هذا وكأنه لغز. للمطور الخبير، إنها مهمة لتحسين هيكل المشروع والأداء. في هذه المقالة، لن نجيب على هذا السؤال فحسب، بل سنركز على تحسين الأداء — الأمران اللذان يجعلان Rust محبوبًا للغاية.

\n\n

سنشرح كيفية تنظيم هذه الحزمة بشكل صحيح، وتجنب تكرار الكود، وتسريع التجميع، وجعل تطبيقك سريعًا حقًا. هيا بنا!

\n\n

هيكل الحزمة: Cargo.toml هو الحل

\n\n

في Rust، يمكن لحزمة Cargo واحدة أن تحتوي على مكتبة (lib) والعديد من التطبيقات (bin). هذا ليس سحرًا، بل مجرد اتفاقية بسيطة لهيكل المجلدات. دعنا نلقي نظرة على مثال بسيط.

\n\n

لننشئ مشروعًا:

\n\n
cargo new my_tool --lib
\n\n

الآن، سنعدل ملف Cargo.toml لنحدد بوضوح أن لدينا مكتبة وتطبيقًا:

\n\n
[package]\nname = "my_tool"\nversion = "0.1.0"\nedition = "2021"\n\n[lib]\nname = "my_tool_lib"\npath = "src/lib.rs"\n\n[[bin]]\nname = "my_tool"\npath = "src/main.rs"\n\n[dependencies]\n
\n\n

سيكون هيكل المجلدات كالتالي:

\n\n
my_tool/\n├── Cargo.toml\n├── src/\n│   ├── lib.rs      // المكتبة\n│   └── main.rs     // التطبيق\n
\n\n

الأمر بسيط، أليس كذلك؟ لكن الشيطان يكمن في التفاصيل. إذا قمت بنسخ الكود من المكتبة إلى main.rs، فستحصل على تكرار ومشاكل في الأداء. يجب أن نكون أذكى.

\n\n

النهج الصحيح: التطبيق يستخدم المكتبة

\n\n

يجب على التطبيق استيراد المكتبة كـ crate خارجي. حتى لو كانا في نفس الحزمة. هذا لا يقلل التكرار فحسب، بل يسمح لمترجم Rust بتحسين الكود بشكل أفضل عبر تحسين الربط عبر الوحدات (LTO).

\n\n

مثال src/lib.rs:

\n\n
pub fn calculate(data: &[u64]) -> u64 {\n    data.iter().sum()\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_calculate() {\n        assert_eq!(calculate(&[1, 2, 3]), 6);\n    }\n}\n
\n\n

مثال src/main.rs:

\n\n
use my_tool_lib::calculate;\n\nfn main() {\n    let numbers = vec![10, 20, 30];\n    println!("المجموع: {}", calculate(&numbers));\n}\n
\n\n

لاحظ: نحن نستخدم my_tool_lib — هذا هو الاسم الذي حددناه للمكتبة في Cargo.toml. سيقوم المترجم تلقائيًا بربطهما في تطبيق واحد، مع الحفاظ على النمطية.

\n\n

تحسين الأداء: LTO و inline والاختبارات المعيارية

\n\n

الآن بعد أن أصبح الهيكل جاهزًا، لنتحدث عن الشيء الرئيسي — كيفية استخراج أقصى أداء من حزمتك. لأنك إذا كنت تنشئ أداة لمعالجة البيانات، فكل ميكروثانية لها أهميتها.

\n\n

1. تفعيل تحسين الربط عبر الوحدات (LTO)

\n\n

يسمح LTO للمترجم بتحسين الكود عبر حدود الـ crates. بدون LTO، يتم تحسين الدوال من المكتبة والتطبيق بشكل منفصل. مع LTO، يتم تحسينها ككيان واحد.

\n\n

أضف إلى Cargo.toml:

\n\n
[profile.release]\nlto = true\ncodegen-units = 1\n
\n\n

سيؤدي هذا إلى زيادة وقت التجميع، لكنه سيجعل التطبيق أسرع بشكل ملحوظ. من المهم التأكيد: LTO هو المعيار لإصدارات الإنتاج في Rust.

\n\n

2. استخدام تلميحات inline

\n\n

إذا كانت المكتبة تحتوي على دوال حرجة، يمكنك استخدام #[inline] لتوجيه المترجم لدمج الكود مباشرة في موقع الاستدعاء. هذا يقلل من overhead الاستدعاء ويزيد السرعة.

\n\n

مثال:

\n\n
#[inline(always)]\npub fn fast_calculate(data: &[u64]) -> u64 {\n    data.iter().sum()\n}\n
\n\n

لكن احذر: الإفراط في استخدام inline قد يزيد حجم الكود ويبطئ التجميع. استخدمه فقط للدوال الصغيرة والمتكررة.

\n\n

3. الاختبارات المعيارية (Benchmarks)

\n\n

لقياس الأداء، استخدم إطار criterion أو الاختبارات المعيارية المدمجة في Rust (غير مستقرة). أضف إلى Cargo.toml:

\n\n
[dev-dependencies]\ncriterion = "0.5"\n\n[[bench]]\nname = "my_benchmark"\nharness = false\n
\n\n

مثال لملف benches/my_benchmark.rs:

\n\n
use criterion::{black_box, criterion_group, criterion_main, Criterion};\nuse my_tool_lib::calculate;\n\nfn benchmark_calculate(c: &mut Criterion) {\n    let data: Vec = (0..1000).collect();\n    c.bench_function("calculate 1000", |b| b.iter(|| calculate(black_box(&data))));\n}\n\ncriterion_group!(benches, benchmark_calculate);\ncriterion_main!(benches);\n
\n\n

شغّل الاختبار باستخدام cargo bench. هذا سيعطيك تقارير دقيقة عن الأداء ويساعدك على تحديد الاختناقات.

\n\n

تحسينات إضافية: التجميع الشرطي والميزات

\n\n

لجعل حزمتك مرنة، استخدم ميزات Cargo (features). يمكنك إضافة ميزات اختيارية للمكتبة تفعّل وظائف إضافية في التطبيق.

\n\n

مثال في Cargo.toml:

\n\n
[features]\ndefault = []\nadvanced = []\n\n[dependencies]\nserde = { version = "1.0", optional = true }\n
\n\n

في الكود:

\n\n
#[cfg(feature = "advanced")]\npub fn advanced_calculate(data: &[u64]) -> u64 {\n    // كود متقدم\n}\n
\n\n

هذا يسمح للمستخدمين بتخصيص الحزمة حسب احتياجاتهم دون تحميل غير ضروري.

\n\n

الخلاصة: الأداء يبدأ من الهيكل

\n\n

إنشاء حزمة Rust تحتوي على مكتبة وتطبيق ليس مجرد تنظيم للملفات. إنه قرار يؤثر على الأداء وقابلية الصيانة. باتباع النهج الصحيح — استخدام المكتبة من التطبيق، تفعيل LTO، استخدام inline بحكمة، وإجراء الاختبارات المعيارية — يمكنك بناء أدوات سريعة وموثوقة.

\n\n

في PythonLib، نؤمن بأن Rust هو مستقبل البرمجة عالية الأداء. جرب هذه التقنيات في مشروعك القادم، وشاركنا نتائجك!

المدونات

توصيات الكتب