في مسألة السرعة وقياسها في اردوينو



نشأت هذه المشكلة في دراسة أداء Arduino عند تنفيذ أوامر مختلفة (المزيد عن ذلك في منشور منفصل). في سياق الدراسة ، نشأت شكوك حول ثبات وقت العمل للأوامر الفردية عندما تغيرت قيمة المعاملات (كما اتضح لاحقًا ، ليس غير معقول) وتم اتخاذ قرار لمحاولة تقدير وقت تنفيذ أمر منفصل. لهذا ، تم كتابة برنامج صغير (قال أن الرسم هو مغادرة الفصل) ، والذي ، للوهلة الأولى ، أكد الفرضية. في الختام ، يمكنك ملاحظة القيمتين 16 و 20 ، ولكن في بعض الأحيان يتم العثور على 28 وحتى 32 ميكروثانية. إذا قمنا بضرب البيانات المستلمة في 16 (تردد ساعة MK) ، نحصل على وقت التنفيذ في دورات MK (من 256 إلى 512). لسوء الحظ ، فإن التشغيل المتكرر لدورة البرنامج الرئيسية (مع نفس البيانات الأولية) ، مع الحفاظ على الصورة الإجمالية ، يعطي بالفعل توزيعًا مختلفًا لوقت التنفيذ ، لذلك لا ترتبط اختلافات الوقت الفعلية بالبيانات الأولية. الفرضية الأصلية مرفوضة ، لكنها تصبح مثيرة للاهتمام ، وما يرتبط بالضبط بمثل هذا الانتثار الهام.

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

لذا ، الوقت يتغير ، والأهم من ذلك أننا نبحث عن أسباب هذه الظاهرة. بادئ ذي بدء ، نولي اهتماما لتعدد القيم التي تم الحصول عليها ، ونلقي نظرة على وصف مكتبة العمل بمرور الوقت ونرى أن 4µsec هو كم من القياس ، لذا من الأفضل أن نذهب إلى الكم ونفهم أننا نحصل على 4 أو 5 (في كثير من الأحيان) و 6 أو 7 أو 8 وحدات (نادرة جدا). في النصف الأول ، كل شيء سهل - إذا كانت القيمة المقاسة تقع بين 4 و 5 وحدات ، يصبح المبعثر أمرًا لا مفر منه. علاوة على ذلك ، بالنظر إلى أن العينات مستقلة ، يمكننا زيادة دقة القياس بالطرق الإحصائية ، وهو ما نقوم به ، والحصول على نتائج مقبولة.

ولكن مع النصف الثاني (6،7،8) كانت الأمور أسوأ. وجدنا أن المبعثر لا يرتبط ببيانات المصدر ، مما يعني أن هذا هو مظهر من مظاهر العمليات الأخرى التي تؤثر على وقت تنفيذ الأوامر. وتجدر الإشارة إلى أن الانبعاثات نادرة نوعًا ما ولا تظهر تأثيرًا كبيرًا على متوسط ​​القيمة المحسوبة. سيكون من الممكن إهمالهم على الإطلاق ، لكن هذا ليس أسلوبنا. بشكل عام ، على مدار سنوات من العمل في الهندسة ، أدركت أنه لا يمكنك ترك سوء الفهم ، بغض النظر عن مدى عدم أهميتها ، نظرًا لأن لديهم قدرة مقرفة على الضرب في الظهر (جيدًا ، أو في أي مكان آخر يصلون إليه) في أكثر الأوقات غير المناسبة.

نبدأ في طرح الفرضية 1 - الأكثر ملاءمة (في الراحة والتنوع تأتي في المرتبة الثانية بعد التدخل المباشر من الخالق) - أعطال البرامج ، بالطبع ، ليس لي ، برامجي لا تفشل أبدًا ، ولكن المكتبات المتصلة (مترجم ، نظام تشغيل ، متصفح ، إلخ. - استبدال ما يلزم). علاوة على ذلك ، نظرًا لأنني أقوم بتشغيل البرنامج في المحاكي على www.tinkercad.com ، فلا يزال بإمكانك الرجوع إلى أخطاء المحاكي وإغلاق الموضوع ، لأن شفرة المصدر غير متاحة لنا. سلبيات هذه الفرضية:

  1. من دورة إلى دورة ، يتغير موقع الانحرافات ، مما يلمح.
  2. لا يزال هذا الموقع يدعم AutoDesk ، على الرغم من أن الحجة ضعيفة إلى حد ما.
  3. "لقد قبلنا الافتراض القائل بأن ما يحدث ليس هذيانا ، وإلا فإنه سيكون غير مثير للاهتمام."

الافتراض التالي هو تأثير بعض عمليات الخلفية على نتيجة القياس. لا يبدو أننا نفعل أي شيء سوى الإيمان ، على الرغم من أننا نخرج النتائج في المسلسل. تنشأ الفرضية 2 - وقت الإخراج أحيانًا (غريب هكذا ... لكنه يحدث) يضاف إلى وقت تنفيذ الأمر. على الرغم من أنه من المشكوك فيه مقدار هذا الإنتاج ، ولكن على أي حال - لم تساعد إضافة Flush ، مما أدى إلى تأخير لإكمال الإخراج ولم يساعد ، بشكل عام نقل الإخراج من الحلقة - على أي حال ، يقفز الوقت - وهذا بالتأكيد ليس تسلسليًا.

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

الفرضية 3 - أحيانًا تستغرق المكالمة الثانية لحساب الوقت وقتًا أطول من الأولى ، أو تؤثر الإجراءات المرتبطة بالعد الزمني أحيانًا على النتيجة. نحن ننظر إلى شفرة المصدر لوظيفة العمل مع الوقت (arduino-1.8.4 \ hardware \ arduino \ avr \ cores \ arduino \ wiring.c - لقد عبرت مرارًا وتكرارًا عن موقفي تجاه هذه الأشياء ، لن أكرر نفسي) ونرى أن مرة واحدة من 256 يتم قطع دورات زيادة الأجهزة في الجزء الأصغر من العداد لزيادة الجزء القديم من العداد.

وقت تنفيذ الدورة لدينا هو من 4 إلى 5 ، لذلك يمكننا أن نتوقع 170 * (4..5) / 256 = من ثلاث إلى أربع قيم شاذة في مقطع من 170 قياسًا. نحن ننظر - يبدو متشابهًا جدًا ، هناك بالفعل 4 منهم. لفصل السببين الأول والثاني ، نقوم بإجراء حسابات حسب القسم الحرج مع الانقطاعات المحظورة. والنتيجة لا تتغير كثيرًا ، ولا يزال هناك مكان للانبعاثات ، مما يعني أن الوقت الإضافي يتم توفيره بالمايكرو (). هنا لا يمكننا فعل أي شيء ، على الرغم من توفر شفرة المصدر ، لا يمكننا تغييره - يتم تضمين المكتبات في الثنائيات. بالطبع ، يمكننا كتابة وظائفنا الخاصة للعمل مع الوقت ومراقبة سلوكهم ، ولكن هناك طريقة أبسط.

نظرًا لأن السبب المحتمل لزيادة المدة هو معالجة الانقطاع "الطويلة" ، فإننا نستبعد إمكانية حدوثها أثناء عملية القياس. للقيام بذلك ، انتظر مظهره وعندها فقط نقوم بإجراء دورة قياس. نظرًا لأن الانقطاع يحدث في كثير من الأحيان أقل مما تستمر دورة القياس لدينا ، يمكننا ضمان عدم وجوده. نكتب الجزء المقابل من البرنامج (باستخدام الاختراق القذرة مع المعلومات المستخرجة من التعليمات البرمجية المصدر) ، و "هذا سحر الشارع" ، كل شيء يصبح عاديًا - نقيس وقت التنفيذ 4 و 5 كوان بمتوسط ​​قيمة وقت تنفيذ عملية الإضافة باستخدام PT لدورات ساعة 166 ، والتي يتوافق مع القيمة المقاسة سابقا. يمكن اعتبار الفرضية مؤكدة.

يبقى سؤال واحد - وما الذي يستغرق وقتا طويلا في الانقطاعات ، ما الذي يتطلبه
(7.8) - (5) ~ 2 كوانت = * 4 = 8 مللي ثانية * 16 = 128 دورة معالج؟ ننتقل إلى شفرة المصدر (أي إلى رمز المجمع الذي تم إنشاؤه بواسطة المترجم على godbolt.com) ونرى أن المقاطعة نفسها تُنفذ ما يقرب من 70 دورة ، 60 منها باستمرار ، وعند القراءة هناك تكاليف إضافية من 10 دورات ، المجموع 70 عند الضغط على انقطاع - أقل من تلقي ، لكنه قريب بما فيه الكفاية. نعزو الفرق إلى الفرق بين المترجمين أو طرق استخدامها.

حسنًا ، يمكننا الآن قياس وقت التنفيذ الفعلي لأمر إضافة PT باستخدام وسيطات مختلفة والتأكد من أنه يتغير كثيرًا عند تغيير الوسيطات: من 136 قياسًا لـ 0.0 إلى 190 لـ 0.63 (الرقم السحري) ، وهذا هو 162 فقط لـ 10.63. مع احتمال بنسبة 99.9٪ ، يرجع ذلك إلى الحاجة إلى التوافق وميزات تنفيذه في هذه المكتبة بالذات ، ولكن من الواضح أن هذه الدراسة تتجاوز نطاق المشكلة قيد النظر.

ملحق - نص البرنامج:
void setup() { Serial.begin(9600); } volatile float t; //   void loop() { int d[170]; unsigned long time,time1; float dt=1/170.; for (int i=0; i<170; ++i) { { //       time1=micros(); long time2; do { time2=micros(); } while ((time2 & ~0xFF) == (time1 & ~0xFF)); }; /**/ time1=micros(); //   /* cli(); //       -   */ t=10.63; //     t=t+dt; //   /* sei(); //    */ time = micros(); //   time1=time-time1; d[i]=time1/4; /* Serial.print(time1); //      Serial.flush(); //     Delay(20); //    */ }; //   ,     float sum=0; for (int i=0; i<170; ++i) { sum+=d[i]; Serial.println(d[i]); }; Serial.println((sum/170-2.11)*4*16); //2.11Serial.flush(); //    ,     } 

Source: https://habr.com/ru/post/ar426941/


All Articles