Странное явление, связанное с микроархитектурой процессора - PullRequest
0 голосов
/ 23 мая 2018

Я делаю тест на измерение стоимости одного вызова функции через указатель, вот мой код .Однако я нашел что-то очень странное и ищу вашу помощь.

Код скомпилирован в режиме Release VS2017 с использованием конфигурации по умолчанию.

Имеется 4 тестовых стенда, и все их ОС Win10.Вот некоторые подробные сведения:

  • M1: CPU: i7-7700, микроархитектура: Kaby Lake
  • M2: CPU: i7-7700, микроархитектура: Kaby Lake
  • M3: CPU: i7-4790, микроархитектура: Haswell
  • M4: CPU: E5-2698 v3, микроархитектура: Haswell

На рисунках ниже условные обозначения представлены в виде machine parameter_order alias.machine указано выше.parameter_order описывает порядок LOOP, переданный программе за один запуск.alias указывает на то, какая часть времени.no-exec означает отсутствие вызова части функции, иначе.Строка 98—108.exec означает вызов части функции, иначе.Строка 115—125.per-exec - стоимость вызова функции.Все единицы времени - миллисекунды.per-exec относится к левой оси Y, в то время как другие относятся к правой оси Y.

Сравнивая Рис.1 - Рис.4, вы можете увидеть, что график может относиться к микроархитектуре ЦП (M1 и M2аналогично, M3 и M4 похожи).

Мои вопросы :

  1. Почему все машины имеют два этапа (LOOP < 25 и LOOP > 100)?
  2. Почему все время без исполнения имеет странный пик, когда 32 <= LOOP <= 41?
  3. Почему время без исполнения и время исполнения машин Kaby Lake (M1 и M2) имеют прерывистый интервалкогда 72 <= LOOP <= 94?
  4. Почему M4 (серверный процессор) имеет большую дисперсию по сравнению с M3 (процессор для настольных ПК)?

Вот мои результаты теста:

Fig. 1

Fig. 2

Fig. 3

Fig. 4

Для удобства я также вставляю код здесь:

#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <algorithm>

#include <windows.h>

using namespace std;

const int PMAX = 11000000, ITER = 60000, RULE = 10000;
//const int LOOP = 10;

int func1(int a, int b, int c, int d, int e)
{
    return 0;
}

int func2(int a, int b, int c, int d, int e)
{
    return 0;
}

int func3(int a, int b, int c, int d, int e)
{
    return 0;
}

int func4(int a, int b, int c, int d, int e)
{
    return 0;
}

int func5(int a, int b, int c, int d, int e)
{
    return 0;
}

int func6(int a, int b, int c, int d, int e)
{
    return 0;
}

int (*init[6])(int, int, int, int, int) = {
    func1,
    func2,
    func3,
    func4,
    func5,
    func6
};
int (*pool[PMAX])(int, int, int, int, int);

LARGE_INTEGER freq;

void getTime(LARGE_INTEGER *res)
{
    QueryPerformanceCounter(res);
}

double delta(LARGE_INTEGER begin_time, LARGE_INTEGER end_time)
{
    return (end_time.QuadPart - begin_time.QuadPart) * 1000.0 / freq.QuadPart;
}

int main()
{
    char path[100], tmp[100];

    FILE *fin, *fout;

    int cnt = 0;

    int i, j, t, r;
    int ans;

    int LOOP;

    LARGE_INTEGER begin_time, end_time;
    double d1, d2, res;

    for(i = 0;i < PMAX;i += 1)
        pool[i] = init[i % 6];

    QueryPerformanceFrequency(&freq);

    printf("file path:");
    scanf("%s", path);

    fin = fopen(path, "r");

start:
    if (fscanf(fin, "%d", &LOOP) == EOF)
        goto end;

    ans = 0;
    getTime(&begin_time);
    for(r = 0;r < RULE;r += 1)
    {
        for(t = 0;t < ITER;t += 1)
        {
            //ans ^= (pool[t])(0, 0, 0, 0, 0);
            ans ^= pool[0](0, 0, 0, 0, 0);
            ans = 0;
            for(j = 0;j < LOOP;j += 1)
                ans ^= j;
        }
    }
    getTime(&end_time);
    printf("%.10f\n", d1 = delta(begin_time, end_time));
    printf("ans:%d\n", ans);

    ans = 0;
    getTime(&begin_time);
    for(r = 0;r < RULE;r += 1)
    {
        for(t = 0;t < ITER;t += 1)
        {
            ans ^= (pool[t])(0, 0, 0, 0, 0);
            ans ^= pool[0](0, 0, 0, 0, 0);
            ans = 0;
            for(j = 0;j < LOOP;j += 1)
                ans ^= j;
        }
    }
    getTime(&end_time);
    printf("%.10f\n", d2 = delta(begin_time, end_time));
    printf("ans:%d\n", ans);

    printf("%.10f\n", res = (d2 - d1) / (1.0 * RULE * ITER));

    sprintf(tmp, "%d.txt", cnt++);

    fout = fopen(tmp, "w");
    fprintf(fout, "%d,%.10f,%.10f,%.10f\n", LOOP, d1, d2, res);
    fclose(fout);

    goto start;
end:

    fclose(fin);

    system("pause");

    exit(0);
}

1 Ответ

0 голосов
/ 23 мая 2018

Почему все машины имеют два этапа (LOOP <25 и LOOP> 100)?

Первый разрыв возникает, когда самый внутренний цикл, for(j = 0;j < LOOP;j += 1), перестает правильно предсказывать свой выход,На моей машине это происходит, когда LOOP достигает 24 итераций.

Вы можете увидеть это довольно ясно с помощью perf stat -I3000, чтобы чередовать выходные данные теста с характеристиками производительности:

BenchWithFixture/RandomTarget/21     727779 ns     727224 ns       3851   78.6834M items/s
    45.003283831        2998.636997      task-clock (msec)                                           
    45.003283831                118      context-switches          #    0.039 K/sec                  
    45.003283831                  0      cpu-migrations            #    0.000 K/sec                  
    45.003283831                  0      page-faults               #    0.000 K/sec                  
    45.003283831      7,777,209,518      cycles                    #    2.595 GHz                    
    45.003283831     26,846,680,371      instructions              #    3.45  insn per cycle         
    45.003283831      6,711,087,432      branches                  # 2238.882 M/sec                  
    45.003283831          1,962,643      branch-misses             #    0.03% of all branches        
BenchWithFixture/RandomTarget/22     751421 ns     750758 ns       3731   76.2169M items/s
    48.003487573        2998.943341      task-clock (msec)                                           
    48.003487573                111      context-switches          #    0.037 K/sec                  
    48.003487573                  0      cpu-migrations            #    0.000 K/sec                  
    48.003487573                  0      page-faults               #    0.000 K/sec                  
    48.003487573      7,778,285,186      cycles                    #    2.595 GHz                    
    48.003487573     26,956,175,646      instructions              #    3.47  insn per cycle         
    48.003487573      6,738,461,171      branches                  # 2247.947 M/sec                  
    48.003487573          1,973,024      branch-misses             #    0.03% of all branches        
BenchWithFixture/RandomTarget/23     774490 ns     773955 ns       3620   73.9325M items/s
    51.003697814        2999.024360      task-clock (msec)                                           
    51.003697814                105      context-switches          #    0.035 K/sec                  
    51.003697814                  0      cpu-migrations            #    0.000 K/sec                  
    51.003697814                  0      page-faults               #    0.000 K/sec                  
    51.003697814      7,778,570,598      cycles                    #    2.595 GHz                    
    51.003697814     21,547,027,451      instructions              #    2.77  insn per cycle         
    51.003697814      5,386,175,806      branches                  # 1796.776 M/sec                  
    51.003697814         72,207,066      branch-misses             #    1.12% of all branches        
BenchWithFixture/RandomTarget/24    1138919 ns    1138088 ns       2461   50.2777M items/s
    57.004129981        2999.003582      task-clock (msec)                                           
    57.004129981                108      context-switches          #    0.036 K/sec                  
    57.004129981                  0      cpu-migrations            #    0.000 K/sec                  
    57.004129981                  0      page-faults               #    0.000 K/sec                  
    57.004129981      7,778,509,575      cycles                    #    2.595 GHz                    
    57.004129981     19,061,717,197      instructions              #    2.45  insn per cycle         
    57.004129981      4,765,017,648      branches                  # 1589.492 M/sec                  
    57.004129981        103,398,285      branch-misses             #    1.65% of all branches        
BenchWithFixture/RandomTarget/25    1171572 ns    1170748 ns       2391   48.8751M items/s
    60.004325775        2998.547350      task-clock (msec)                                           
    60.004325775                111      context-switches          #    0.037 K/sec                  
    60.004325775                  0      cpu-migrations            #    0.000 K/sec                  
    60.004325775                  0      page-faults               #    0.000 K/sec                  
    60.004325775      7,777,298,382      cycles                    #    2.594 GHz                    
    60.004325775     17,008,954,992      instructions              #    2.19  insn per cycle         
    60.004325775      4,251,656,734      branches                  # 1418.230 M/sec                  
    60.004325775        131,311,948      branch-misses             #    2.13% of all branches

Перед переходом коэффициент ошибочного прогнозирования ветвления составляет около 0,03%, а затем он скачетпримерно до 2,13% справа, когда тест замедляется, или увеличение на два порядка.Уровень ошибочных прогнозов на самом деле немного ниже, чем вы ожидаете: с 25 ответвлениями (плюс еще пара для внешних циклов) вы ожидаете около 1 / 25 == 4% ошибочных прогнозов, но мы не видим этого, не зная почему.

На моей машине первый цикл (только с вызовом pool[0](0,0,0,0,0)), как и ваш, не имеет перехода при ~ 24 LOOP итерациях, но почему мне не понятно.Мой опыт показывает, что счетчики TAGE обычно не могут обрабатывать циклы с постоянной итерацией длиннее 24-х циклов, но здесь может быть некоторое взаимодействие с косвенным предиктором ветвления.Это довольно интересно.

Почему все время без исполнения имеет странный пик, когда 32 <= LOOP <= 41? </p>

Я также испытал это локально.В моем тесте это также было связано с неправильными прогнозами в ветвях: когда время шло, ошибались и прогнозы.Опять же, как работает прогнозирование (так хорошо), здесь неясно, но, очевидно, при этих значениях алгоритм получает ошибки прогнозирования.

Почему время без исполнения и время исполнения у машин Kaby Lake(M1 и M2) имеют прерывистый интервал, когда 72 <= LOOP <= 94? </p>

Я испытал то же самое: итерация 72 работала со скоростью 28M циклов в секунду, а итерация 73 только со скоростью 20M (ипоследующие итерации тоже были медленными).Опять же, различие может быть связано с ошибочными прогнозами ветвлений: они увеличились с 0,01% до 1,35% с итерации 72 до 73. Это почти точно одно неверное прогнозирование на выполнение внешнего цикла, поэтому, скорее всего, это неверный прогноз при выходе.

Почему M4 (серверный процессор) имеет большую дисперсию по сравнению с M3 (процессор для настольных ПК)?

Ваш тест очень длинный, поэтому в нем много измененийиспытывать различные источники дисперсии, такие как прерывания, переключение контекста, изменения частоты ядра и так далее.

Поскольку это совершенно другая аппаратная и, возможно, программная конфигурация, неудивительно, что наблюдается неравномерное отклонение.Вы можете уменьшить количество внешних итераций, чтобы сократить эталонный тест и посмотреть, уменьшится ли количество выбросов (но их величина увеличится).Вы также можете переместить синхронизацию во внешнем цикле, поэтому вы синхронизируете меньшую секцию и посмотрите на гистограмму, чтобы увидеть, как результаты различных систем распределяются за этот меньший интервал.

Для более глубокого взгляда на источникиДисперсия и как их диагностировать, посмотрите на этот ответ .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...