Показатели ЦП (промахи/попадания кэша), которые не имеют смысла

Я использую Intel PCM для детальных измерений ЦП. В своем коде я пытаюсь измерить эффективность кеша.

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

PCM показывает мне, что у меня довольно высокий коэффициент промаха L2 и L3. Я также проверил с rdtscp, и циклы на операцию с массивом составляют 15 (что намного выше, чем 4-5 циклов для доступа к кешу L1).

Я ожидаю, что массив будет полностью помещен в кеш L1, и у меня не будет высокого коэффициента промахов L1, L2 и L3.

Моя система имеет 32K, 256K и 25M для L1, L2 и L3 соответственно. Вот мой код:

static const int ARRAY_SIZE = 16;

struct MyStruct {
    struct MyStruct *next;
    long int pad;
}; // each MyStruct is 16 bytes

int main() {
    PCM * m = PCM::getInstance();
    PCM::ErrorCode returnResult = m->program(PCM::DEFAULT_EVENTS, NULL);
    if (returnResult != PCM::Success){
        std::cerr << "Intel's PCM couldn't start" << std::endl;
        exit(1);
    }

    MyStruct *myS = new MyStruct[ARRAY_SIZE];

    // Make a sequential liked list,
    for (int i=0; i < ARRAY_SIZE - 1; i++){
        myS[i].next = &myS[i + 1];
        myS[i].pad = (long int) i;
    }
    myS[ARRAY_SIZE - 1].next = NULL;
    myS[ARRAY_SIZE - 1].pad = (long int) (ARRAY_SIZE - 1);

    // Filling the cache
    MyStruct *current;
    for (int i = 0; i < 200000; i++){
        current = &myS[0];
        while ((current = current->n) != NULL)
            current->pad += 1;
    }

    // Sequential access experiment
    current = &myS[0];
    long sum = 0;

    SystemCounterState before = getSystemCounterState();

    while ((current = current->n) != NULL) {
        sum += current->pad;
    }

    SystemCounterState after = getSystemCounterState();

    cout << "Instructions per clock: " << getIPC(before, after) << endl;
    cout << "Cycles per op: " << getCycles(before, after) / ARRAY_SIZE << endl;
    cout << "L2 Misses:     " << getL2CacheMisses(before, after) << endl;
    cout << "L2 Hits:       " << getL2CacheHits(before, after) << endl; 
    cout << "L2 hit ratio:  " << getL2CacheHitRatio(before, after) << endl;
    cout << "L3 Misses:     " << getL3CacheMisses(before_sstate,after_sstate) << endl;
    cout << "L3 Hits:       " << getL3CacheHits(before, after) << endl;
    cout << "L3 hit ratio:  " << getL3CacheHitRatio(before, after) << endl;

    cout << "Sum:   " << sum << endl;
    m->cleanup();
    return 0;
}

Это результат:

Instructions per clock: 0.408456
Cycles per op:        553074
L2 Cache Misses:      58775
L2 Cache Hits:        11371
L2 cache hit ratio:   0.162105
L3 Cache Misses:      24164
L3 Cache Hits:        34611
L3 cache hit ratio:   0.588873

EDIT: я также проверил следующий код и по-прежнему получаю те же коэффициенты промаха (которые, как я ожидал, получат почти нулевые коэффициенты промахов):

SystemCounterState before = getSystemCounterState();
// this is just a comment
SystemCounterState after = getSystemCounterState();

EDIT 2: как предположил один из комментариев, эти результаты могут быть связаны с накладными расходами самого профилировщика. Таким образом, вместо одного раза я много раз (200 000 000 раз) менял код обхода массива, чтобы амортизировать накладные расходы профилировщика. Я по-прежнему получаю очень низкие коэффициенты кэша L2 и L3 (% 15).


person narengi    schedule 16.05.2015    source источник
comment
В вашем эксперименте (измеряемом цикле while) всего 16 итераций. Вероятно, в измерениях преобладают накладные расходы и возмущения функции getSystemCounterState.   -  person papirrin    schedule 16.05.2015
comment
Я бы порекомендовал сравнить L2/LLC промахи/попадания со счетчиками попаданий L1. Вы можете обнаружить, что у вас мало M попаданий L1 против 50K промахов L2.   -  person Elalfer    schedule 10.07.2015


Ответы (1)


Кажется, что вы получаете промахи l2 и l3 от всех ядер в вашей системе.

Я смотрю на реализацию PCM здесь: /erikarn/intel-pcm/blob/ecc0cf608dfd9366f4d2d9fa48dc821af1c26f33/src/cpucounters.cpp

[1] в реализации PCM::program() в строке 1407 я не вижу кода, ограничивающего события конкретным процессом

[2] в реализации PCM::getSystemCounterState() в строке 2809 видно, что события собираются со всех ядер вашей системы. Поэтому я бы попытался установить привязку процессора процесса к одному ядру, а затем читать события только с этого ядра - с помощью этой функции CoreCounterState getCoreCounterState(uint32 core)

person effenok    schedule 25.07.2015
comment
Все верно - PCM по умолчанию получает информацию от всех ядер вашей системы. - person BeeOnRope; 22.06.2016