
نشأت هذه المشكلة في دراسة أداء 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 ، فلا يزال بإمكانك الرجوع إلى أخطاء المحاكي وإغلاق الموضوع ، لأن شفرة المصدر غير متاحة لنا. سلبيات هذه الفرضية:
- من دورة إلى دورة ، يتغير موقع الانحرافات ، مما يلمح.
- لا يزال هذا الموقع يدعم AutoDesk ، على الرغم من أن الحجة ضعيفة إلى حد ما.
- "لقد قبلنا الافتراض القائل بأن ما يحدث ليس هذيانا ، وإلا فإنه سيكون غير مثير للاهتمام."
الافتراض التالي هو تأثير بعض عمليات الخلفية على نتيجة القياس. لا يبدو أننا نفعل أي شيء سوى الإيمان ، على الرغم من أننا نخرج النتائج في المسلسل. تنشأ
الفرضية 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(); // t=10.63; // t=t+dt; // time = micros(); // time1=time-time1; d[i]=time1/4; }; // , 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.11 – Serial.flush(); // , }