كيفية الحفاظ على مساحة المكدس مع التصميم الجيد؟

StackOverflow https://stackoverflow.com/questions/135262

سؤال

أنا أقوم بالبرمجة بلغة C لذاكرة الوصول العشوائي (RAM) لوحدة التحكم الدقيقة المدمجة المحدودة مع RTOS.

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

هل هناك بديل للحفاظ على التعليمات البرمجية منظمة بشكل جيد وقابلة للقراءة، مع الحفاظ على الذاكرة؟

هل كانت مفيدة؟

المحلول

حاول أن تجعل مكدس الاستدعاءات مسطحًا، بدلاً من ذلك a() الاتصال b() الذي يدعو c() الذي يدعو d(), ، يملك a() يتصل b(), c(), ، و d() بحد ذاتها.

إذا تمت الإشارة إلى الدالة مرة واحدة فقط، فقم بوضع علامة عليها inline (على افتراض أن المترجم الخاص بك يدعم هذا).

نصائح أخرى

هناك 3 مكونات لاستخدام المكدس الخاص بك:

  • عناوين إرجاع استدعاءات الوظائف
  • معلمات استدعاء الوظيفة
  • المتغيرات التلقائية (المحلية).

إن مفتاح تقليل استخدام المكدس الخاص بك هو تقليل تمرير المعلمات والمتغيرات التلقائية.استهلاك المساحة لاستدعاء الوظيفة الفعلية بحد ذاته ضئيل للغاية.

حدود

إحدى الطرق لمعالجة مشكلة المعلمة هي تمرير بنية (عبر المؤشر) بدلاً من عدد كبير من المعلمات.


foo(int a, int b, int c, int d)
{
...
   bar(int a, int b);
}

افعل هذا بدلاً من ذلك:


struct my_params {
   int a;
   int b;
   int c;
   int d;
};
foo(struct my_params* p)
{
   ...
   bar(p);
};

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

المتغيرات التلقائية (المحلية)

يميل هذا إلى أن يكون أكبر مستهلك لمساحة المكدس.

  • المصفوفات هي القاتل.لا تحدد المصفوفات في وظائفك المحلية!
  • تقليل عدد المتغيرات المحلية.
  • استخدم أصغر نوع ضروري.
  • إذا لم تكن إعادة الدخول مشكلة، فيمكنك استخدام المتغيرات الثابتة للوحدة.

ضع في اعتبارك أنه إذا كنت تقوم ببساطة بنقل جميع المتغيرات المحلية من النطاق المحلي إلى نطاق الوحدة، فلن تقوم بحفظ أي مساحة.لقد قمت بتداول مساحة المكدس لمساحة مقطع البيانات.

تدعم بعض أنظمة RTOS التخزين المحلي للخيط، والذي يخصص التخزين "العالمي" على أساس كل خيط.قد يسمح لك هذا بوجود عدة متغيرات عمومية مستقلة على أساس كل مهمة، ولكن هذا سيجعل التعليمات البرمجية الخاصة بك غير واضحة.

في حالة أنه يمكنك توفير قدر كبير من الذاكرة الرئيسية ولكن ليس لديك سوى قطعة صغيرة من المكدس، أقترح تقييم التخصيصات الثابتة.

في لغة C، تتم "إدارة" جميع المتغيرات المعلنة داخل الدالة تلقائيًا، مما يعني أنها مخصصة على المكدس.

يؤدي تأهيل الإعلانات على أنها "ثابتة" إلى تخزينها في الذاكرة الرئيسية بدلاً من وضعها على المكدس.إنها تتصرف بشكل أساسي مثل المتغيرات العالمية ولكنها تسمح لك بتجنب العادات السيئة التي تأتي مع الإفراط في استخدام المتغيرات العالمية.يمكنك تقديم حالة جيدة للإعلان عن المخازن المؤقتة/المتغيرات الكبيرة وطويلة العمر على أنها ثابتة لتقليل الضغط على المكدس.

احذر من أن هذا لا يعمل بشكل جيد/على الإطلاق إذا كان تطبيقك متعدد الخيوط أو إذا كنت تستخدم العودية.

قم بتشغيل التحسين، وتحديدًا البطانة العدوانية.يجب أن يكون المترجم قادرًا على تضمين الأساليب لتقليل المكالمات.اعتمادًا على المترجم ومفاتيح التحسين التي تستخدمها، يتم وضع علامة على بعض الطرق على أنها inline قد يساعد (أو قد يتم تجاهله).

مع دول مجلس التعاون الخليجي، حاول إضافة علامة "-finline-functions" (أو -O3) وربما علامة "-finline-limit=n".

إحدى الحيل التي قرأتها في مكان ما لتقييم متطلبات المكدس للكود في الإعداد المضمن هي ملء مساحة المكدس في البداية بنمط معروف (DEAD في السداسي هو المفضل لدي) والسماح للنظام بالعمل لفترة من الوقت.

بعد التشغيل العادي، اقرأ مساحة المكدس ولاحظ مقدار مساحة المكدس التي لم يتم استبدالها أثناء سير العملية.التصميم بحيث يترك ما لا يقل عن 150% من ذلك وذلك لمعالجة جميع مسارات التعليمات البرمجية الغامضة التي ربما لم يتم ممارستها.

هل يمكنك استبدال بعض المتغيرات المحلية الخاصة بك بالمتغيرات العالمية؟يمكن للمصفوفات على وجه الخصوص أن تلتهم المكدس.

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

تؤدي تكلفة المقايضة إلى زيادة التعقيد وزيادة خطر حدوث آثار جانبية غير مرغوب فيها بين الوظائف مقابل بصمة ذاكرة أصغر.

ما نوع المتغيرات الموجودة في وظائفك؟ما الأحجام والحدود التي نتحدث عنها؟

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

كما هو مذكور في الإجابات الأخرى، يعد التضمين أحد الخيارات المتاحة على الرغم من أن ذلك يأتي على حساب حجم أكبر للكود.

المنطقة الأخرى التي تأكل المكدس هي المعلمات المحلية.هذه المنطقة لديك بعض السيطرة عليها.سيؤدي استخدام الإحصائيات (على مستوى الملف) إلى تجنب تخصيص المكدس على حساب تخصيص ذاكرة الوصول العشوائي الثابتة.العولمة كذلك.

في الحالات القصوى (حقًا)، يمكنك التوصل إلى اتفاقية للوظائف التي تستخدم عددًا ثابتًا من المتغيرات العامة كتخزين مؤقت بدلاً من المتغيرات المحلية على المكدس.الشيء الصعب هو التأكد من عدم استدعاء أي من الوظائف التي تستخدم نفس العناصر العالمية في نفس الوقت.(وبالتالي الاتفاقية)

إذا كنت بحاجة إلى البدء في الحفاظ على مساحة المكدس، فيجب عليك إما الحصول على مترجم أفضل أو ذاكرة أكبر.

عادةً ما ينمو برنامجك (ميزات جديدة،...)، لذا إذا كان عليك بدء مشروع من خلال التفكير في كيفية الحفاظ على مساحة المكدس، فهذا محكوم عليه بالفشل من البداية.

نعم، يمكن لنظام RTOS أن يستهلك ذاكرة الوصول العشوائي (RAM) لاستخدام مكدس المهام.تجربتي هي أنه كمستخدم جديد لنظام RTOS، هناك ميل لاستخدام مهام أكثر من اللازم.

بالنسبة لنظام مضمن يستخدم نظام RTOS، يمكن أن تكون ذاكرة الوصول العشوائي (RAM) سلعة ثمينة.للحفاظ على ذاكرة الوصول العشوائي (RAM)، بالنسبة للميزات البسيطة، لا يزال من الممكن تنفيذ العديد من الميزات ضمن مهمة واحدة، وتشغيلها بطريقة دائرية، مع تصميم تعاوني متعدد المهام.وبالتالي تقليل العدد الإجمالي للمهام.

أعتقد أنك قد تتخيل مشكلة غير موجودة هنا.لا يقوم معظم المترجمين فعليًا بأي شيء عندما "يخصصون" متغيرات تلقائية على المكدس.

يتم تخصيص المكدس قبل تنفيذ "main()".عند استدعاء الدالة b() من الدالة a()، يتم تمرير عنوان منطقة التخزين مباشرة بعد آخر متغير يستخدمه a إلى b().يصبح هذا بداية مكدس b() إذا قام b() باستدعاء الدالة c() ثم يبدأ مكدس c بعد آخر متغير تلقائي محدد بواسطة b().

لاحظ أن ذاكرة المكدس موجودة بالفعل ومخصصة، ولم تتم أي عملية تهيئة وأن المعالجة الوحيدة المتضمنة هي تمرير مؤشر مكدس.

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

هناك خدعة أخرى للأنظمة المحتوية على الذاكرة وهي تقسيم أجزاء الذاكرة من الوظيفة إلى وظائف مستقلة مستقلة.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top