_Mm_clflush действительно очищает кеш? - PullRequest
0 голосов
/ 26 сентября 2018

Я пытаюсь понять, как работает аппаратный кеш, написав и запустив тестовую программу:

#include <stdio.h>
#include <stdint.h>
#include <x86intrin.h>

#define LINE_SIZE   64

#define L1_WAYS     8
#define L1_SETS     64
#define L1_LINES    512

// 32K memory for filling in L1 cache
uint8_t data[L1_LINES*LINE_SIZE];

int main()
{
    volatile uint8_t *addr;
    register uint64_t i;
    int junk = 0;
    register uint64_t t1, t2;

    printf("data: %p\n", data);

    //_mm_clflush(data);
    printf("accessing 16 bytes in a cache line:\n");
    for (i = 0; i < 16; i++) {
        t1 = __rdtscp(&junk);
        addr = &data[i];
        junk = *addr;
        t2 = __rdtscp(&junk) - t1;
        printf("i = %2d, cycles: %ld\n", i, t2);
    }
}

Я запускаю код с и без _mm_clflush, в то время как результаты просто показываютс _mm_clflush первый доступ к памяти быстрее.

с _mm_clflush:

$ ./l1
data: 0x700c00
accessing 16 bytes in a cache line:
i =  0, cycles: 280
i =  1, cycles: 84
i =  2, cycles: 91
i =  3, cycles: 77
i =  4, cycles: 91

без _mm_clflush:

$ ./l1
data: 0x700c00
accessing 16 bytes in a cache line:
i =  0, cycles: 3899
i =  1, cycles: 91
i =  2, cycles: 105
i =  3, cycles: 77
i =  4, cycles: 84

Это просто делаетне имеет смысла очищать строку кэша, но на самом деле становится быстрее?Кто-нибудь может объяснить, почему это происходит?Спасибо

---------------- Дальнейший эксперимент -------------------

Предположим, что 3899 циклов вызваны пропуском TLB.Чтобы доказать свои знания о попадании в кэш, я немного изменил этот код, чтобы сравнить время доступа к памяти в случае L1 cache hit и L1 cache miss.

На этот раз код пропускает размер строки кэша (64 байта) и обращается к следующему адресу памяти.

*data = 1;
_mm_clflush(data);
printf("accessing 16 bytes in a cache line:\n");
for (i = 0; i < 16; i++) {
    t1 = __rdtscp(&junk);
    addr = &data[i];
    junk = *addr;
    t2 = __rdtscp(&junk) - t1;
    printf("i = %2d, cycles: %ld\n", i, t2);
}

// Invalidate and flush the cache line that contains p from all levels of the cache hierarchy.
_mm_clflush(data);
printf("accessing 16 bytes in different cache lines:\n");
for (i = 0; i < 16; i++) {
    t1 = __rdtscp(&junk);
    addr = &data[i*LINE_SIZE];
    junk = *addr;
    t2 = __rdtscp(&junk) - t1;
    printf("i = %2d, cycles: %ld\n", i, t2);
}

Поскольку мой компьютер имеет 8-канальный набор ассоциированного кэша данных L1 с 64 наборами, всего 32 КБ.Если я получаю доступ к памяти каждые 64 байта, это должно вызывать все пропуски кэша.Но кажется, что уже есть много строк кэширования:

$ ./l1
data: 0x700c00
accessing 16 bytes in a cache line:
i =  0, cycles: 273
i =  1, cycles: 70
i =  2, cycles: 70
i =  3, cycles: 70
i =  4, cycles: 70
i =  5, cycles: 70
i =  6, cycles: 70
i =  7, cycles: 70
i =  8, cycles: 70
i =  9, cycles: 70
i = 10, cycles: 77
i = 11, cycles: 70
i = 12, cycles: 70
i = 13, cycles: 70
i = 14, cycles: 70
i = 15, cycles: 140
accessing 16 bytes in different cache lines:
i =  0, cycles: 301
i =  1, cycles: 133
i =  2, cycles: 70
i =  3, cycles: 70
i =  4, cycles: 147
i =  5, cycles: 56
i =  6, cycles: 70
i =  7, cycles: 63
i =  8, cycles: 70
i =  9, cycles: 63
i = 10, cycles: 70
i = 11, cycles: 112
i = 12, cycles: 147
i = 13, cycles: 119
i = 14, cycles: 56
i = 15, cycles: 105

Это вызвано предварительной выборкой?Или что-то не так с моим пониманием?Спасибо

Ответы [ 3 ]

0 голосов
/ 15 июля 2019

Я добавляю дополнительную ссылку к data перед строкой _mm_clflush(data), и она показывает, что clflush очищает строку кэша.Модифицированный код:

#include <stdio.h>
#include <stdint.h>
#include <x86intrin.h>

#define LINE_SIZE   64
#define L1_WAYS     8
#define L1_SETS     64
#define L1_LINES    512

// 32K memory for filling in L1 cache
uint8_t data[L1_LINES*LINE_SIZE];

int main()
{
    volatile uint8_t *addr;
    register uint64_t i;
    int junk = 0;
    register uint64_t t1, t2;

    printf("data: %p", data);
    data[0] = 1;
    //_mm_clflush(data);

    printf("accessing 16 bytes in a cache line:\n");
    for (i = 0; i < 16; i++) {
        t1 = __rdtscp(&junk);
        addr = &data[i];
        junk = *addr;
        t2 = __rdtscp(&junk) - t1;
        printf("i = %2d, cycles: %ld\n", i, t2);
    }
}

Я запустил модифицированный код на своем компьютере (процессор Intel® Core (TM) i5-8500) и получил:

без clflush:

data: 0000000000407980
accessing 16 bytes in a cache line:
i =  0, cycles: 64
i =  1, cycles: 46
i =  2, cycles: 49
i =  3, cycles: 48
i =  4, cycles: 46

с clflush:

data: 0000000000407980
accessing 16 bytes in a cache line:
i =  0, cycles: 214
i =  1, cycles: 41
i =  2, cycles: 40
i =  3, cycles: 42
i =  4, cycles: 40
0 голосов
/ 15 июля 2019

Без clflush первая загрузка занимает около 3899 циклов, что примерно равно времени, необходимому для обработки незначительной ошибки страницы.rdtscp сериализует операции загрузки, тем самым гарантируя, что все последующие загрузки в одну и ту же строку попадут в кэш L1.Теперь, когда вы добавляете clflush непосредственно перед циклом, ошибка страницы запускается и обрабатывается вне цикла.Когда обработчик ошибок страницы возвращается и clflush перезапускается, целевая строка кэша очищается.На процессорах Intel rdtscp гарантирует, что линия очищается перед первой загрузкой в ​​цикле.Таким образом, первая загрузка в денежной иерархии будет отсутствовать, и ее задержка будет примерно равна времени доступа к памяти.Как и в предыдущем случае, последующие загрузки сериализуются как rdtscp, и поэтому все они попадают в L1D.

Измеренные задержки L1D слишком высоки, хотя, даже если мы рассмотрим издержки rdtscp,Вы скомпилировали с -O3?

Я не смог воспроизвести ваши результаты (т. Е. Незначительную ошибку страницы) с gcc 5.5.0 в Linux 4.4.0-154, когда строка кэша размещена статически, но только когда я использую mmap.Если вы сообщите мне свою версию компилятора и версию ядра, возможно, я смогу исследовать это подробнее.

Что касается вашего второго вопроса, то, как вы измеряете задержку нагрузки, не позволит вам различить попадание L1D и L2попадание, потому что погрешность измерения может быть такой же большой, как разница в задержках.Вы можете использовать счетчики производительности MEM_LOAD_UOPS_RETIRED.L1_HIT и MEM_LOAD_UOPS_RETIRED.L2_HIT для проверки.Шаблон последовательного доступа довольно легко обнаруживается аппаратными устройствами предварительной выборки L1 и L2, поэтому неудивительно получать попадания, если вы не отключите устройства предварительной выборки.

0 голосов
/ 27 сентября 2018

Я думаю, это может быть вызвано пропуском TLB в начале?И _mm_clflush на самом деле кеширует этот виртуальный адрес в TLB, возможно, я прав?Как это доказать?

...