Я протестировал следующий код с платформой 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](https://i.stack.imgur.com/IuqpN.png)
На этой картинке произошло много разных вещей, и, к сожалению, я не могу объяснить все изменения в графике.
Я тестировал этот код на одноядерном процессоре со следующей конфигурацией кэшей:
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
На этих рисунках мы видим много изменений в поведении графика:
- Наблюдается всплеск размера массива в 64 байта, что можно объяснить тем фактом, что размер строки кэша составляет 64 байта, а с массивом размером более 64 байтов мы испытываем еще одну ошибку кэша L1 (которую можно классифицировать как обязательный промах тайника)
- Кроме того, задержка увеличивается вблизи границ размера кеша, что также легко объяснимо - в этот момент мы наблюдаем пропускную способность кеша
Но есть много вопросов о результатах, которые я не могу объяснить:
- Почему задержка для
MemoryAccessEvery4th
уменьшается после того, как массив превысил ~ 1024 байта?
- Почему мы видим еще один пик для
MemoryAccessAllElements
около 512 байт? Это интересный момент, потому что в этот момент мы начали получать доступ к более чем одному набору строк кэша (8 * 64 байта в одном наборе). Но действительно ли это вызвано этим событием, и если да, то как это можно объяснить?
- Почему мы можем видеть увеличение задержки после прохождения размера кэша 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();
Вы можете найти несколько другие примеры в хранилище , но на самом деле базовый подход к тесту в вопросе тот же.