Понимание кеширования процессора - PullRequest
0 голосов
/ 11 ноября 2018

Я протестировал следующий код с платформой Google Benchmark для измерения задержки доступа к памяти при разных размерах массива:

int64_t MemoryAccessAllElements(const int64_t *data, size_t length) {
    for (size_t id = 0; id < length; id++) {
        volatile int64_t ignored = data[id];
    }
    return 0;
}
int64_t MemoryAccessEvery4th(const int64_t *data, size_t length) {
    for (size_t id = 0; id < length; id += 4) {
        volatile int64_t ignored = data[id];
    }
    return 0;
}

И я получаю следующие результаты (результаты усредняются по бенчмарку Google, для больших массивов около 10 итераций и гораздо больше работы выполняется для меньших):

Benchmark results

На этой картинке произошло много разных вещей, и, к сожалению, я не могу объяснить все изменения в графике.

Я тестировал этот код на одноядерном процессоре со следующей конфигурацией кэшей:

CPU Caches:                                                                                              
  L1 Data 32K (x1), 8 way associative
  L1 Instruction 32K (x1), 8 way associative
  L2 Unified 256K (x1), 8 way associative
  L3 Unified 30720K (x1), 20 way associative

На этих рисунках мы видим много изменений в поведении графика:

  1. Наблюдается всплеск размера массива в 64 байта, что можно объяснить тем фактом, что размер строки кэша составляет 64 байта, а с массивом размером более 64 байтов мы испытываем еще одну ошибку кэша L1 (которую можно классифицировать как обязательный промах тайника)
  2. Кроме того, задержка увеличивается вблизи границ размера кеша, что также легко объяснимо - в этот момент мы наблюдаем пропускную способность кеша

Но есть много вопросов о результатах, которые я не могу объяснить:

  1. Почему задержка для MemoryAccessEvery4th уменьшается после того, как массив превысил ~ 1024 байта?
  2. Почему мы видим еще один пик для MemoryAccessAllElements около 512 байт? Это интересный момент, потому что в этот момент мы начали получать доступ к более чем одному набору строк кэша (8 * 64 байта в одном наборе). Но действительно ли это вызвано этим событием, и если да, то как это можно объяснить?
  3. Почему мы можем видеть увеличение задержки после прохождения размера кэша L2 при тестировании MemoryAccessEvery4th, но с MemoryAccessAllElements?

Я пытался сравнить свои результаты с результатами галереи эффектов кэша процессора и , что каждый программист должен знать о памяти , но я не могу полностью описать свои результаты с рассуждениями из этой статьи.

Может кто-нибудь помочь мне понять внутренние процессы кэширования процессора?

UPD: Я использую следующий код для измерения производительности доступа к памяти:

#include <benchmark/benchmark.h>
using namespace benchmark;
void InitializeWithRandomNumbers(long long *array, size_t length) {
    auto random = Random(0);
    for (size_t id = 0; id < length; id++) {
        array[id] = static_cast<long long>(random.NextLong(0, 1LL << 60));
    }
}
static void MemoryAccessAllElements_Benchmark(State &state) {
    size_t size = static_cast<size_t>(state.range(0));
    auto array = new long long[size];
    InitializeWithRandomNumbers(array, size);
    for (auto _ : state) {
        DoNotOptimize(MemoryAccessAllElements(array, size));
    }
    delete[] array;
}
static void CustomizeBenchmark(benchmark::internal::Benchmark *benchmark) {
    for (int size = 2; size <= (1 << 24); size *= 2) {
        benchmark->Arg(size);
    }
}
BENCHMARK(MemoryAccessAllElements_Benchmark)->Apply(CustomizeBenchmark);
BENCHMARK_MAIN();

Вы можете найти несколько другие примеры в хранилище , но на самом деле базовый подход к тесту в вопросе тот же.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...