Я пытаюсь продемонстрировать ассоциативность кэша на процессорах Intel, обращаясь к памяти с шагом, равным степени двух.Я получаю доступ к матрице двойных значений K x N по столбцам (в C ++) и ожидаю, что при K> 8 производительность будет ухудшаться, если N - степень двух (потому что я работаю на процессорах с 8-сторонним интерфейсом).установить ассоциативный кеш L1).Поэтому я строю график доступа к памяти для K = 1,40, для N = 2 ^ 20, N = 2 ^ 20 + 8 и N = 2 ^ 20 + 64, таким образом делая заполнение строк кэша 0, 1 и 8соответственно.
#include <cstdio>
#include <cstdlib>
#include <chrono>
double buffer[((1 << 20) + 64) * 40];
void measure_flops(int N, int K) {
// Warm-up.
for (int j = 0; j < 10; ++j)
for (int i = 0; i < N * K; ++i)
buffer[i] += 1e-10 * i + 0.123 * j;
// Iterate.
int repeat = 500 / K;
auto start = std::chrono::steady_clock::now();
for (int r = 0; r < repeat; ++r)
for (int i = 0; i < N; ++i)
for (int j = 0; j < K; ++j)
buffer[j * N + i] += 1.0123;
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> diff = end - start;
volatile double tmp = buffer[rand() % (N * K)]; (void)tmp;
// Report.
double flops = (double)repeat * N * K / diff.count();
printf("%d %2d %.4lf\n", N, K, flops * 1e-9);
fflush(stdout);
}
void run(int N) {
printf(" N K GFLOPS\n");
for (int K = 1; K <= 40; ++K)
measure_flops(N, K);
printf("\n\n");
}
int main() {
const int N = 1 << 20;
run(N);
run(N + 64 / sizeof(double));
run(N + 512 / sizeof(double));
return 0;
}
Я запускаю тест на следующих процессорах:
- Intel Xeon E5-2680 v3 (Haswell, ассоциативность равна 8, 8, 20 для L1, L2и L3 соответственно)
- Intel Xeon E5-2670 v3 (Haswell, ассоциативность составляет 8, 8, 20)
- Intel Xeon Gold 6150 (Skylake, ассоциативность составляет 8, 16, 11)
... и я получаю противоречивые результаты и некоторые формы, которые я не понимаю.
Мои вопросы:
1) Почему производительность не восстановилась для заполнения = 64 B для E5-2680, если для E5-2670?Производительность восстанавливается для заполнения = 512 B, но почему?(Возможно ли, что prefetcher удаляет кэш?)
EDIT: протестировано также на E5-2690 и E5-2650, на другом кластере.E5-2690 ведет себя как E5-2680 (плохая производительность для заполнения = 64 B), в то время как E5-2650 ведет себя как E5-2670 (хорошая производительность).Таким образом, кажется, что между 2670 и 2680 действительно что-то изменилось.
2) Почему производительность падает так рано, до того, как К достигает 8?(Я подозреваю, что это «фиктивная стойка переадресации магазина», как объяснено в несколько иной конфигурации здесь , но я не уверен.)
3) Почему тогда снова падает производительностьдля K = ~ 32 для E5-2680 и для K = ~ 35 для золота 6150?(perf stat -ddd
показывает увеличенное количество пропусков кэша L3, но я не вижу точной причины, потому что K не соответствует ассоциативности L3.)
Подробности настройки:
E5-2680 и Gold 6150 работают в кластере, где я использую полный узел, чтобы убедиться, что нет помех другим пользователям.Узел содержит 2 ЦП в обоих случаях, но я в любом случае использую только одно ядро.E5-2670 находится на общей машине с одним ЦП (не может получить полный узел), но запустил тест, когда он был неактивен.
Я скомпилировал код с g++ -O1 -std=c++11
(версия 6.3.0 для E5-2680 и Gold, версия 7.3.0 для E5-2670).Все результаты воспроизводимы.Изменение компилятора (или, например, добавление -O3
или добавление volatile
к buffer
) изменяет более или менее только результаты для низкого K, например, K = 2 становится быстрее / медленнее.