Значения счетчика, которые вы видите, не являются предварительной выборкой L1: эффект сохраняется, даже если вы отключите предварительные выборки L1. Фактически, эффект остается, если вы отключите все предварительные сборщики, кроме стримера L2:
wrmsr -a 0x1a4 "$((2#1110))"
Если вы делаете отключение стримера L2, однако, количество будет таким, как вы ожидаете: вы увидите примерно 1 000 000 L2.RFO_MISS
и L2.RFO_ALL
даже без mfence
.
Во-первых, важно отметить, что количество событий L2_RQSTS.RFO_*
не учитывает события RFO, исходящие от стримера L2 . Вы можете увидеть подробности здесь , но в основном umask для каждого из событий RFO 0x24:
name umask
RFO_MISS 0x22
RFO_HIT 0x42
ALL_RFO 0xE2
Обратите внимание, что ни одно из значений umask не имеет бита 0x10
, который указывает, что события, которые происходят от стримера L2, должны отслеживаться.
Похоже, что когда стример L2 активен, многие из событий, которые можно ожидать назначить одному из этих событий, вместо этого "съедаются" событиями предварительной выборки L2. Вероятно, случается так, что предварительная выборка L2 выполняется раньше, чем поток запросов, и когда запрос RFO поступает из L1, он находит запрос, уже выполняемый предварительным выборщиком L2. Это только снова увеличивает версию события umask |= 0x10
(на самом деле я получаю 2 000 000 ссылок, включая этот бит), что означает, что RFO_MISS
, RFO_HIT
и RFO_ALL
пропустят его.
Это в некоторой степени аналогично сценарию "fb_hit", где L1 не загружает ни попадания, ни попадания точно, но поражает текущую загрузку - но сложность здесь заключается в том, что загрузка была инициирована средством предварительной выборки L2.
The mfence
только замедляет все вниз достаточно, что префетчер L2 почти всегда имеет время, чтобы довести линию вплоть до L2, давая RFO_HIT
кол.
Я не думаю, что здесь используются средства предварительной выборки L1 (на это указывает тот факт, что это работает так же, если вы их отключите): насколько я знаю, средства предварительной выборки L1 не взаимодействуют с магазинами, а только загружаются.
Вот несколько полезных perf
команд, которые вы можете использовать, чтобы увидеть разницу, в том числе и с битом «L2 streamer origin». Вот без событий стримера L2:
perf stat --delay=1000 -e cpu/event=0x24,umask=0xef,name=l2_rqsts_references/,cpu/event=0x24,umask=0xe2,name=l2_rqsts_all_rfo/,cpu/event=0x24,umask=0xc2,name=l2_rqsts_rfo_hit/,cpu/event=0x24,umask=0x22,name=l2_rqsts_rfo_miss/
и вместе с ними:
perf stat --delay=1000 -e cpu/event=0x24,umask=0xff,name=l2_rqsts_references/,cpu/event=0x24,umask=0xf2,name=l2_rqsts_all_rfo/,cpu/event=0x24,umask=0xd2,name=l2_rqsts_rfo_hit/,cpu/event=0x24,umask=0x32,name=l2_rqsts_rfo_miss/
Я запустил их для этого кода (с выравниванием sleep(1)
с командой --delay=1000
, переданной perf, чтобы исключить код инициализации):
#include <time.h>
#include <immintrin.h>
#include <stdio.h>
#include <unistd.h>
typedef struct _object{
int value;
char pad[60];
} object;
int main() {
volatile object * array;
int arr_size = 1000000;
array = (object *) malloc(arr_size * sizeof(object));
for(int i=0; i < arr_size; i++){
array[i].value = 1;
_mm_clflush((const void*)&array[i]);
}
_mm_mfence();
sleep(1);
// printf("Starting main loop after %zu ms\n", (size_t)clock() * 1000u / CLOCKS_PER_SEC);
int tmp;
for(int i=0; i < arr_size-105; i++){
array[i].value = 2;
//tmp = array[i].value;
// _mm_mfence();
}
}