Сборка: вычислить время выполнения инструкций - PullRequest
1 голос
/ 25 октября 2011

Как вы вычисляете время выполнения инструкций? Это просто делается путем проверки того, что говорят производители микросхем, с точки зрения того, сколько тактовых циклов может выполнить действие? Есть что-нибудь еще, что я должен знать об этом? Такое ощущение, что я что-то упустил ....

Ответы [ 2 ]

4 голосов
/ 09 ноября 2011

Насколько мне известно, инструкция RDTSC является чрезвычайно точной.

Я думаю, что если вы ищете точное число циклов, то в случае коротких загрузочных разделов вы можете столкнуться с проблемами одновременности, что Mysticialупомянуто ...

Но если ультра-ультра-ультра-ультра-точность не является препятствием ... то есть, если вы можете выжить, зная, что для определенных сценариев ваш результат отклоняется ...Я не знаю ... скажем, от 9 до 80 циклов ... тогда я почти уверен, что вы все еще можете получить очень точные результаты с помощью RDTSC ... особенно, если учесть, что 9 - 80, деленное на 3,2 миллиарда, - очень крошечное число :)

Числа 9 и 80 были выбраны немного произвольно (и, возможно, вы не используете скорость процессора 3,2 ГГц), так как я не знаю точно, что такое количество ошибок ... но я уверен, что этов этом приблизительном примере:)

Вот выдержка RDTSC из функции таймера, которую я использую:

//High-Rez Setup
__asm
{
    push        eax
    push        edx
    rdtsc
    mov         [AbsoluteLow],eax
    mov         [AbsoluteHigh],edx
    pop         edx
    pop         eax
}

на самом деле я пойду дальше и опубликую все это ... этот код предполагает, чтотипа "двойной"является 64-битным числом с плавающей запятой ... которое не может быть универсальным предположением компилятора / архитектуры:

double              AbsoluteTime;
double              AbsoluteResolution;
ulong               AbsoluteLow;
ulong               AbsoluteHigh;



void Get_AbsoluteTime (double *time)
{
    //Variables
    double  current, constant;
    double  lower, upper;
    ulong   timelow, timehigh;

    //Use the Intel RDTSC
    __asm
    {
        push    eax
        push    edx
        rdtsc
        sub     eax, [AbsoluteLow]
        sbb     edx, [AbsoluteHigh]
        mov     [timelow], eax
        mov     [timehigh], edx
        pop     edx
        pop     eax
    }

    //Convert two 32bit registers to a 64-bit floating point
    //Multiplying by 4294967296 is similar to left-shifting by 32 bits
    constant     = 4294967296.0;
    lower        = (double) timelow;
    upper        = (double) timehigh;
    upper       *= constant;
    current      = lower + upper;
    current     /= AbsoluteResolution;
    current     += AbsoluteTime;
    *time        = current;
}



void Set_AbsoluteTime (double time, double scale)
{
    //Variables
    double  invScale;

    //Setup
    AbsoluteTime = time;

    //High-Rez Setup
    __asm
    {
        push    eax
        push    edx
        rdtsc
        mov     [AbsoluteLow],eax
        mov     [AbsoluteHigh],edx
        pop     edx
        pop     eax
    }

    //Fetch MHZ
    if (1)
    {
        //Local Variables
        int      nv;
        ulong    mhz;
        char     keyname[2048];

        //Default assumption of 3.2ghz if registry functions fail
        mhz = 3200;

        //Registry Key
        sprintf (keyname, "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0");
        nv = Reg_Get_ValueDW (keyname, "~MHz", (ulong *)&mhz);

        //Transform into cycles per second
        mhz *= 1000000;

        //Calculate Speed Stuff
        AbsoluteResolution = (double) mhz;
        invScale  = 1.0;
        invScale /= scale;
        AbsoluteResolution *= invScale;
    }
}

Вы хотите где-нибудь вызвать Set_AbsoluteTime перед использованием функций Get ... без первого начального вызоваУстановите, Gets будет возвращать ошибочные результаты ... но как только этот разовый вызов сделан, вы можете идти ...

вот пример:

void Function_to_Profile (void)
{
    //Variables
    double   t1, t2, TimeElapsed;

    //Profile operations
    Get_AbsoluteTime (&t1);
    ...do stuff here...
    Get_AbsoluteTime (&t2);

    //Calculate Elapsed Time
    TimeElapsed = (t2 - t1);

    //Feedback
    printf ("This function took %.11f seconds to run\n", TimeElapsed);
}

void main (void)
{
    Set_AbsoluteTime (0.000, 1.000);
    Function_to_Profile();
}

если по какой-то причине вычтобы измерения времени проходили в обратном направлении на половинной скорости (возможно, удобно для программирования игр), начальный вызов будет следующим: Set_AbsoluteTime (0.000, -0.500);

первый параметр для Set - это базовое время, которое получаетдобавлено ко всем результатам

Я почти уверен, что эти функции более точны, чем большинство общедоступных таймеров Windows API с высоким разрешением, которые существуют в настоящее время ... Я думаю, что на быстрых процессорах ошибка меньше 1 наносекунды, ноЯ не уверен на 100%:)

они достаточно точны для моих целей, но учтите, что стандартная инициализация40 байтов pre-amble (составленных из 'current', 'constant', 'lower', 'upper', 'timelow', 'timehigh'), которые большинство компиляторов C установит в 0xCC или 0xCD, будут использовать некоторые циклы ... какбудет ли математика выполняться в нижней части каждого вызова Get_AbsoluteTime ...

, поэтому для по-настоящему первозданной точности вам лучше всего создать кадры того, что вы хотите профилировать в «встроенных» RDTSC ... Я бы использовалрасширенный регистр x64 для хранения ответа для последующих операций вычитания вместо того, чтобы возиться с более медленным доступом к памяти ...

, как, например, что-то вроде этого ... это в основном концепция, кстати, потому что технически VC2010не позволяет вам генерировать x64-Assembly с помощью ключевого слова __asm ​​:( ... но я думаю, что это даст вам концептуальную дорогу:

typedef unsigned long long ulonglong;
ulonglong Cycles;

__asm
{
    push rax
    push rdx
    rdtsc
    mov r9, edx
    shl r9, 32
    and rax, 0xFFFFFFFF
    or  r9, rax
    pop rdx
    pop rax
}

...Perform stuff to profile here

__asm
{
    push rax
    push rdx
    rdtsc
    mov r10, edx
    shl r10, 32
    and rax, 0xFFFFFFFF
    or  r10, rax
    sub r10, r9
    mov qword ptr [Cycles], r10
    pop rdx
    pop rax
}

printf ("The code took %s cycles to execute\n", ULONGLONG_TO_STRING (Cycles));

с этим кодом, я думаю, что окончательный ответчисло прошедших циклов будет в r10, 64-битном регистре ... или в Cycles, 64-битном целом числе без знака ... с горсткой циклов ошибки, вызванной биt операции сдвига и стека ... при условии, что профилируемый код не уничтожает r9 и r10 хе-хе ... Я забыл, что наиболее стабильны регистры расширенного x64 ...

также "и rax, 0xFFFFFFFF "может быть посторонним, потому что я не могу вспомнить, обнуляет ли RDTSC верхние 32 бита RAX или нет ... поэтому я включил эту операцию И на всякий случай:)

1 голос
/ 25 октября 2011

Это нетривиальное задание. Самый простой способ - просто посмотреть результаты того, что нашли другие.

Например, Agner Fog - отличный справочник по этой информации о современных процессорах x86 / x64: http://www.agner.org/optimize/instruction_tables.pdf

Если вы действительно хотите измерить задержки инструкций и пропускную способность самостоятельно, вам понадобятся очень глубокие знания о том, как работают процессоры. И тогда вам придется погрузиться в код сборки. Написание микро-тестов для измерения этих вещей - почти само по себе поле, так как для этого требуется множество методов обратного проектирования.

И, конечно же, в конечном итоге - производительность приложения зависит от многих других факторов, а не только от задержек инструкций / пропускной способности ...

...