تخزين البيانات مؤقتًا في Rust: عندما تحدد السرعة كل شيء
\nتخيل: تطبيق Rust الخاص بك يعالج آلاف الطلبات في الثانية، لكنه يعيد حساب نفس البيانات في كل مرة. قاعدة البيانات تئن، المستخدمون ينتظرون، وأنت تشعر بانخفاض الأداء. موقف مألوف؟ التخزين المؤقت هو طوق النجاة الذي يحول التطبيق البطيء إلى محرك نفاث. في هذه المقالة، سنتعرف على كيفية تخزين البيانات مؤقتًا بكفاءة في Rust باستخدام الإمكانيات المدمجة والكريتات الشائعة. لا سحر - فقط كود نقي وسريع.
\nتشتهر Rust بأدائها، لكنها ليست محصنة ضد الاختناقات. التخزين المؤقت في Rust ليس مجرد تقنية، بل فلسفة: لا تقم بعمل غير ضروري. سننظر في عدة طرق: من HashMap البسيط إلى LRU-cache المتقدم وحلول لسيناريوهات تعدد المهام. جهز المترجم - لننطلق!
لماذا التخزين المؤقت للبيانات في Rust أمر لا غنى عنه؟
\nفي عالم البرمجة النظامية، يحل التخزين المؤقت للبيانات في Rust ثلاث مهام رئيسية:
\n- \n
- تقليل زمن الاستجابة - الوصول إلى الذاكرة أسرع 100-1000 مرة من القراءة من القرص أو طلب عبر الشبكة. \n
- توفير الموارد - حمل أقل على وحدة المعالجة المركزية وقاعدة البيانات وواجهات برمجة التطبيقات الخارجية. \n
- التوسع - يسمح الكاش بتحمل الأحمال القصوى دون انهيار. \n
لكن Rust يضيف خصائصه الخاصة: نظام الملكية والاقتراض الصارم يتطلب الدقة عند العمل مع البيانات المشتركة. إذا كنت تكتب بلغة Rust، فأنت معتاد على التحكم، لكن التخزين المؤقت هو الحالة التي تحتاج فيها إلى تخفيف التحكم قليلاً (بأمان بالطبع).
\n\nالتخزين المؤقت الأساسي باستخدام HashMap: بسيط وفعال
\nالطريقة الأكثر وضوحًا لتخزين البيانات مؤقتًا في Rust هي استخدام HashMap. إنه مثالي لكميات صغيرة من البيانات حيث تكون المفاتيح فريدة ويكون عمر الكاش محدودًا بخيط واحد.
إليك مثال لكاش بسيط لنتائج العمليات الحسابية:
\nuse std::collections::HashMap;\n\nstruct Cache {\n store: HashMap<String, i32>,\n}\n\nimpl Cache {\n fn new() -> Self {\n Cache {\n store: HashMap::new(),\n }\n }\n\n fn get_or_compute(&mut self, key: &str, compute: fn() -> i32) -> i32 {\n if let Some(&value) = self.store.get(key) {\n return value;\n }\n let value = compute();\n self.store.insert(key.to_string(), value);\n value\n }\n}\n\nfn main() {\n let mut cache = Cache::new();\n let result = cache.get_or_compute("expensive_data", || {\n // محاكاة عملية حسابية ثقيلة\n std::thread::sleep(std::time::Duration::from_millis(100));\n 42\n });\n println!("النتيجة: {}", result);\n}\nيوضح هذا الكود الأساس: نتحقق من وجود المفتاح، إذا لم يكن موجودًا - نحسب ونخزن. لكن لهذه الطريقة عيوب: الكاش ينمو دون رقابة، ولا توجد آلية للتنظيف. للإنتاج، نحتاج إلى شيء أكثر ذكاءً.
\n\nعندما يخذلنا HashMap
\nتخيل أن تطبيقك يعمل لأيام. سينمو HashMap حتى يستهلك كل الذاكرة. الحل هو LRU-cache (الأقل استخدامًا مؤخرًا)، الذي يزيل الإدخالات القديمة.
\n\nLRU-cache في Rust: حل أنيق للذاكرة المحدودة
\nLRU-cache هو المعيار الذهبي لتخزين البيانات مؤقتًا في Rust. يخزن عددًا ثابتًا من العناصر ويزيل تلقائيًا تلك التي تم الوصول إليها بشكل أقل. في Rust، يوجد كريت ممتاز لهذا الغرض وهو lru.
أضف إلى Cargo.toml:
[dependencies]\nlru = "0.12"\nالآن مثال على الاستخدام:
\nuse lru::LruCache;\nuse std::num::NonZeroUsize;\n\nfn main() {\n // كاش لـ 3 عناصر\n let mut cache = LruCache::new(NonZeroUsize::new(3).unwrap());\n\n cache.put("واحد", 1);\n cache.put("اثنان", 2);\n cache.put("ثلاثة", 3);\n\n // نصل إلى "واحد" لجعله "م