كيف يمكنك قياس الوقت الذي تستغرقه الوظيفة للتنفيذ؟

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

  •  09-06-2019
  •  | 
  •  

سؤال

كيف يمكنك قياس مقدار الوقت الذي ستستغرقه الوظيفة للتنفيذ؟

هذه وظيفة قصيرة نسبيًا ومن المحتمل أن يكون وقت التنفيذ في نطاق المللي ثانية.

يتعلق هذا السؤال بالتحديد بنظام مضمن مبرمج بلغة C أو C++.

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

المحلول

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

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

نصائح أخرى

هناك ثلاثة حلول محتملة:

حل الأجهزة:

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


    *io_pin = 1;
    myfunc();
    *io_pin = 0;

حل دودة الكتب:

إذا كانت الوظيفة صغيرة إلى حد ما، ويمكنك إدارة التعليمات البرمجية المفككة، فيمكنك فتح دفتر بيانات بنية المعالج وحساب الدورات التي سيستغرقها المعالج لتنفيذ كل التعليمات.سيعطيك هذا عدد الدورات المطلوبة.
الوقت = # دورات * معدل ساعة المعالج / دقات الساعة حسب التعليمات

من الأسهل القيام بذلك مع الوظائف الأصغر، أو التعليمات البرمجية المكتوبة في المجمع (على سبيل المثال متحكم PIC)

حل عداد الطابع الزمني:

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

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

لذا:

// begin timing
for (int i = 0; i < 10000; i++) {
    invokeFunction();
}
// end time
// divide by 10000 to get actual time.

إذا كنت تستخدم نظام التشغيل Linux، فيمكنك تحديد وقت تشغيل البرنامج عن طريق كتابة سطر الأوامر:

time [funtion_name]

إذا قمت بتشغيل الوظيفة في main() فقط (بافتراض C++)، فيجب أن يكون باقي وقت التطبيق ضئيلًا.

أكرر استدعاء الوظيفة كثيرًا (بالملايين) ولكني أستخدم أيضًا الطريقة التالية لخصم الحمل الزائد للحلقة:

start = getTicks();

repeat n times {
    myFunction();
    myFunction();
}

lap = getTicks();

repeat n times {
    myFunction();
}

finish = getTicks();

// overhead + function + function
elapsed1 = lap - start;

// overhead + function
elapsed2 = finish - lap;

// overhead + function + function - overhead - function = function
ntimes = elapsed1 - elapsed2;

once = ntimes / n; // Average time it took for one function call, sans loop overhead

بدلاً من استدعاء الدالة() مرتين في الحلقة الأولى ومرة ​​واحدة في الحلقة الثانية، يمكنك فقط استدعائها مرة واحدة في الحلقة الأولى وعدم استدعائها على الإطلاق (أي:حلقة فارغة) في الثانية، ولكن يمكن تحسين الحلقة الفارغة بواسطة المترجم، مما يمنحك نتائج توقيت سلبية :)

start_time = timer
function()
exec_time = timer - start_time

نظام التشغيل Windows XP/NT المضمن أو نظام التشغيل Windows CE/Mobile

يمكنك استخدام QueryPerformanceCounter() للحصول على قيمة عداد سريع جدًا قبل وظيفتك وبعدها.ثم تقوم بطرح قيم 64 بت هذه وتحصل على "علامات التجزئة" دلتا.باستخدام QueryPerformanceCounterFrequency() يمكنك تحويل "علامات التجزئة دلتا" إلى وحدة زمنية فعلية.يمكنك الرجوع إلى وثائق MSDN حول مكالمات WIN32 تلك.

الأنظمة المدمجة الأخرى

بدون أنظمة التشغيل أو مع أنظمة تشغيل أساسية فقط، سيتعين عليك:

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

مهم جدا لا تنسَ التعطيل قبل واستعادة المقاطعات بعد الحصول على قيم المؤقت هذه (قيمة النقل وقيمة التسجيل) وإلا فإنك تخاطر بحفظ قيم غير صحيحة.

ملحوظات

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

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

#include <time.h>
#include <stdio.h>
#include <stdlib.h>

#define SEC_TO_NSEC(s) ((s) * 1000 * 1000 * 1000)

int work_function(int c) {
    // do some work here
    int i, j;
    int foo = 0;
    for (i = 0; i < 1000; i++) {
        for (j = 0; j < 1000; j++) {
            for ^= i + j;
        }
    }
}

int main(int argc, char *argv[]) {
    struct timespec pre;
    struct timespec post;
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &pre);
    work_function(0);
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &post);

    printf("time %d\n",
        (SEC_TO_NSEC(post.tv_sec) + post.tv_nsec) -
        (SEC_TO_NSEC(pre.tv_sec) + pre.tv_nsec));
    return 0;
}

ستحتاج إلى ربط هذا بمكتبة الوقت الفعلي، فقط استخدم ما يلي لتجميع التعليمات البرمجية الخاصة بك:

gcc -o test test.c -lrt

قد ترغب أيضًا في قراءة صفحة الدليل clock_gettime توجد بعض المشكلات في تشغيل هذا الرمز على نظام يستند إلى SMP مما قد يؤدي إلى إبطال الاختبار.يمكنك استخدام شيء من هذا القبيل sched_setaffinity() أو سطر الأوامر cpuset لفرض الكود على نواة واحدة فقط.

إذا كنت تتطلع إلى قياس وقت المستخدم والنظام، فيمكنك استخدام times(NULL) الذي يُرجع شيئًا مثل لمح البصر.أو يمكنك تغيير المعلمة ل clock_gettime() من CLOCK_THREAD_CPUTIME_ID ل CLOCK_MONOTONIC...ولكن كن حذرا من الالتفاف حولها CLOCK_MONOTONIC.

بالنسبة للمنصات الأخرى، أنت وحدك.

رسم

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

مثال:

#define TICK_INTERVAL 1    // milliseconds between ticker interrupts
static unsigned long tickCounter;

interrupt ticker (void)  
{
    tickCounter += TICK_INTERVAL;
    ...
}

unsigned in GetTickCount(void)
{
    return tickCounter;
}

في الكود الخاص بك، ستقوم بتوقيت الكود كما يلي:

int function(void)
{
    unsigned long time = GetTickCount();

    do something ...

    printf("Time is %ld", GetTickCount() - ticks);
}

في محطة OS X (وربما Unix أيضًا)، استخدم "الوقت":

time python function.py

إذا كان الرمز هو .Net، فاستخدم فئة ساعة الإيقاف (.net 2.0+) وليس DateTime.Now.لم يتم تحديث DateTime.Now بدقة كافية وسيعطيك نتائج مذهلة

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

إذا كان Linux مضمنًا، فانظر إلى مؤقتات Linux:

http://linux.die.net/man/3/clock_gettime

Java المضمنة، انظر إلى nanoTime()، على الرغم من أنني لست متأكدًا من وجودها في الإصدار المضمن:

http://java.sun.com/j2se/1.5.0/docs/api/Java/lang/System.html#nanoTime()

إذا كنت تريد الوصول إلى عدادات الأجهزة، فجرب PAPI:

http://icl.cs.utk.edu/papi/

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

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