حساب تردد وحدة المعالجة المركزية في C مع RDTSC دائما إرجاع 0

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

سؤال

تم إعطاء قطعة التعليمات البرمجية التالية من مدربنا حتى نتمكن من قياس بعض أداء الخوارزميات:

#include <stdio.h>
#include <unistd.h>

static unsigned cyc_hi = 0, cyc_lo = 0;

static void access_counter(unsigned *hi, unsigned *lo) {
    asm("rdtsc; movl %%edx,%0; movl %%eax,%1"
    : "=r" (*hi), "=r" (*lo)
    : /* No input */
    : "%edx", "%eax");
}

void start_counter() {
    access_counter(&cyc_hi, &cyc_lo);
}

double get_counter() {
    unsigned ncyc_hi, ncyc_lo, hi, lo, borrow;
    double result;

    access_counter(&ncyc_hi, &ncyc_lo);

    lo = ncyc_lo - cyc_lo;
    borrow = lo > ncyc_lo;
    hi = ncyc_hi - cyc_hi - borrow;

    result = (double) hi * (1 << 30) * 4 + lo;

    return result;
}

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

int main(void)
{
    double c1, c2;

    start_counter();

    c1 = get_counter();
    sleep(1);
    c2 = get_counter();

    printf("CPU Frequency: %.1f MHz\n", (c2-c1)/1E6);
    printf("CPU Frequency: %.1f GHz\n", (c2-c1)/1E9);

    return 0;
}

المشكلة هي أن النتيجة هي دائمًا 0 ولا أستطيع أن أفهم السبب. أقوم بتشغيل Linux (ARCH) كضيف على VMware.

على آلة صديق (MacBook) تعمل إلى حد ما ؛ أعني أن النتيجة أكبر من 0 ولكنها متغيرة لأن تردد وحدة المعالجة المركزية غير ثابت (حاولنا إصلاحه ولكن لسبب ما لا نتمكن من القيام بذلك). لديه جهاز مختلف يعمل Linux (Ubuntu) كمضيف ويبلغ أيضًا عن 0. هذا يستبعد المشكلة الموجودة على الجهاز الظاهري ، والتي اعتقدت أنها كانت المشكلة في البداية.

أي أفكار لماذا يحدث هذا وكيف يمكنني إصلاحه؟

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

المحلول

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

rdtsc
push 1
call sleep
rdtsc

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

rdtsc
rdtsc
push 1
call sleep

في هذه الحالة ، من الواضح لماذا الفرق بين الاثنين rdtscسيكون S (على الأقل قريب جدًا) 0. لمنع ذلك ، تحتاج إلى تنفيذ تعليمات ستقوم بها وحدة المعالجة المركزية مطلقا إعادة ترتيب التنفيذ خارج الترتيب. التعليمات الأكثر شيوعا لاستخدامها CPUID. يجب أن تبدأ الإجابة الأخرى التي ربطتها (إذا كانت الذاكرة) تقريبًا من هناك ، حول الخطوات اللازمة للاستخدام CPUID بشكل صحيح/بفعالية لهذه المهمة.

بالطبع ، من المحتمل أن تيم بوست كان على حق ، وأنت ايضا رؤية المشاكل بسبب الجهاز الظاهري. ومع ذلك ، كما هو الحال الآن ، ليس هناك ما يضمن أن رمزك سيعمل بشكل صحيح حتى على الأجهزة الحقيقية.

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

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

نصائح أخرى

لا أستطيع أن أقول على وجه اليقين ما هو الخطأ بالضبط في الكود الخاص بك ، لكنك تقوم بعمل غير ضروري لمثل هذا التعليمات البسيطة. أنصحك بتبسيط rdtsc رمز بشكل كبير. لا تحتاج إلى القيام بـ 64 بت الرياضيات تحمل نفسك ، ولا تحتاج إلى تخزين نتيجة هذه العملية كمضاعفة. لا تحتاج إلى استخدام مخرجات منفصلة في ASM المضمّن ، يمكنك إخبار GCC باستخدام EAX و EDX.

فيما يلي نسخة مبسطة إلى حد كبير من هذا الرمز:

#include <stdint.h>

uint64_t rdtsc() {
    uint64_t ret;

# if __WORDSIZE == 64
    asm ("rdtsc; shl $32, %%rdx; or %%rdx, %%rax;"
        : "=A"(ret)
        : /* no input */
        : "%edx"
    );
#else
    asm ("rdtsc" 
        : "=A"(ret)
    );
#endif
    return ret;
}

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

أما بالنسبة لـ VMware ، ألق نظرة على الوقت الاحتفاظ بالمواصفات (رابط PDF) ، وكذلك هذا الموضوع. تعليمات TSC هي (اعتمادًا على نظام التشغيل الضيف):

  • انتقل مباشرة إلى الجهاز الحقيقي (ضيف PV)
  • دورات العد في حين يتم تنفيذ VM على معالج المضيف (Windows / etc)

ملاحظة ، في #2 في حين يتم تنفيذ VM على معالج المضيف. ستذهب نفس الظاهرة إلى Xen أيضًا ، إذا كنت أتذكر بشكل صحيح. في جوهرها ، يمكنك أن تتوقع أن يعمل الرمز كما هو متوقع على ضيف قانوني. في حالة محاكاة ، من غير المعقول تمامًا توقع أجهزة مثل الاتساق.

لقد نسيت استخدامها volatile في بيان ASM الخاص بك, ، لذلك أنت تخبر المترجم أن asm البيان ينتج نفس الإخراج في كل مرة ، مثل وظيفة خالصة. ((volatile فقط ضمني ل asm عبارات مع عدم وجود مخرجات.)

هذا ما يفسر لماذا تحصل بالضبط صفر: التحويل المترجم end-start ل 0 في وقت الترجمة ، من خلال CSE (القضاء على الانحدار الشائع).

انظر إجابتي على الحصول على عدد دورة CPU؟ ل __rdtsc() الجواب الجوهري ، و @mysticial هناك يعمل في GNU C INLINE ASM ، والتي سأقتبسها هنا:

// prefer using the __rdtsc() intrinsic instead of inline asm at all.
uint64_t rdtsc(){
    unsigned int lo,hi;
    __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
    return ((uint64_t)hi << 32) | lo;
}

هذا يعمل بشكل صحيح وكفاءة للرمز 32 و 64 بت.

هممم أنا لست إيجابيًا ولكني أظن أن المشكلة قد تكون داخل هذا الخط:

النتيجة = (مزدوج) HI * (1 << 30) * 4 + lo ؛

أنا متشكك إذا كنت تستطيع تنفيذ هذه الضربات الضخمة بأمان في "غير موقعة" ... أليس هذا في كثير من الأحيان رقم 32 بت؟ ... فقط حقيقة أنه لا يمكنك مضاعفة بأمان 2^32 واضطررت إلى إلحاقها بأنها إضافة إضافية "* 4" إلى 2^30 في النهاية تلميحات في هذا الاحتمال ... قد تحتاج إلى ذلك قم بتحويل كل عنصر فرعي HI و LO إلى مزدوج (بدلاً من واحد في النهاية) وقم بالضرب باستخدام الزوجين

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