Во-первых, обратите внимание, что два вызова printf
после измерения diff1
и diff2
могут нарушить состояние L1D и даже L2. В моей системе, с printf
, сообщенные значения для diff3-ov
находятся в диапазоне от 4 до 48 циклов (я настроил свою систему так, чтобы частота TSC была приблизительно равна частоте ядра). Наиболее распространенными значениями являются значения задержек L2 и L3. Если сообщаемое значение равно 8, то мы получили наш кэш L1D. Если оно больше 8, то, скорее всего, предыдущий вызов printf
выбил целевую строку кэша из L1D и, возможно, из L2 (и в некоторых редких случаях из L3!), Что объясняет измеренные задержки, которые выше 8. @PeterCordes предложил использовать (void) *((volatile int*)array + i)
вместо temp = array[i]; printf(temp)
. После внесения этого изменения мои эксперименты показывают, что большинство зарегистрированных измерений для diff3-ov
составляют ровно 8 циклов (что говорит о том, что ошибка измерения составляет около 4 циклов), и единственными другими значениями, которые сообщаются, являются 0, 4 и 12. Таким образом, Настоятельно рекомендуется подход Питера.
В общем, задержка доступа к основной памяти зависит от многих факторов, включая состояние кэшей MMU и влияние обходчиков таблиц страниц на кэши данных, частоту ядра, частоту неосновной памяти, состояние и конфигурацию памяти. контроллер и микросхемы памяти относительно целевого физического адреса, неконкурентного конфликта и конфликта на ядре из-за гиперпоточности. array[70]
может находиться на другой виртуальной странице (и физической странице), чем array[30]
, и их IP-адреса инструкций загрузки и адреса целевых областей памяти могут взаимодействовать с программами предварительной выборки сложным образом. Поэтому может быть много причин, по которым cache miss1
отличается от cache miss2
. Тщательное расследование возможно, но это потребует больших усилий, как вы можете себе представить. Как правило, если частота вашего ядра превышает 1,5 ГГц (что меньше, чем частота TSC на высокопроизводительных процессорах Intel), то потеря загрузки L3 займет не менее 60 тактов ядра. В вашем случае обе задержки превышают 100 циклов, так что это, скорее всего, пропуски L3. Однако в некоторых крайне редких случаях cache miss2
кажется близким к диапазонам задержки L3 или L2, что может быть связано с предварительной выборкой.
Я определил, что следующий код дает статистически более точное измерение в Haswell:
t1 = __rdtscp(&dummy);
tmp = *((volatile int*)array + 30);
asm volatile ("add $1, %1\n\t"
"add $1, %1\n\t"
"add $1, %1\n\t"
"add $1, %1\n\t"
"add $1, %1\n\t"
"add $1, %1\n\t"
"add $1, %1\n\t"
"add $1, %1\n\t"
"add $1, %1\n\t"
"add $1, %1\n\t"
"add $1, %1\n\t"
: "+r" (tmp));
t2 = __rdtscp(&dummy);
t2 = __rdtscp(&dummy);
loadlatency = t2 - t1 - 60; // 60 is the overhead
Вероятность того, что loadlatency
составляет 4 цикла, составляет 97%. Вероятность того, что loadlatency
составляет 8 циклов, составляет 1,7%. Вероятность того, что loadlatency
примет другие значения, составляет 1,3%. Все остальные значения больше 8 и кратны 4. Я постараюсь добавить объяснение позже.