Skip to content

Instantly share code, notes, and snippets.

@fffaraz
Last active June 8, 2026 00:26
Show Gist options
  • Select an option

  • Save fffaraz/a1940bb0f449615624d884f3883ddf28 to your computer and use it in GitHub Desktop.

Select an option

Save fffaraz/a1940bb0f449615624d884f3883ddf28 to your computer and use it in GitHub Desktop.
Memory Benchmark
// g++ -O3 -std=c++17 benchmark.cpp -o benchmark
#include <iostream>
#include <vector>
#include <chrono>
#include <iomanip>
#include <cstdint>
#include <string>
#include <sstream>
#include <random>
#include <algorithm>
using namespace std;
// Helper to format output sizes
string formatSize(size_t bytes) {
const size_t KB = 1024;
const size_t MB = 1024 * KB;
const size_t GB = 1024 * MB;
stringstream ss;
if (bytes >= GB) {
ss << (bytes / GB) << " GB";
} else if (bytes >= MB) {
ss << (bytes / MB) << " MB";
} else {
ss << (bytes / KB) << " KB";
}
return ss.str();
}
int main() {
const size_t KB = 1024;
const size_t MB = 1024 * KB;
const size_t GB = 1024 * MB;
// Start at 4KB, double until 4GB
vector<size_t> sizes;
for (size_t s = 4 * KB; s <= 4ULL * GB; s *= 2) {
sizes.push_back(s);
}
// Increased target bytes to 32 GB for sufficient iterations on the largest arrays
const size_t TARGET_BYTES = 32ULL * GB;
const size_t LATENCY_ITERATIONS = 20000000; // Baseline pointer chases
const size_t STRIDE_BYTES = 64;
cout << setw(15) << left << "Buffer Size"
<< setw(20) << left << "Bandwidth (GB/s)"
<< "Latency (ns)" << "\n";
cout << "--------------------------------------------------------\n";
mt19937_64 rng(123456789);
for (size_t size : sizes) {
// Use size_t for the buffer to cleanly support index chasing
size_t num_elements = size / sizeof(size_t);
vector<size_t> buffer(num_elements);
// ==========================================
// BANDWIDTH MEASUREMENT (Sequential Strided)
// ==========================================
// Fill buffer with pseudo-random data
uint32_t state = 123456789;
for (size_t i = 0; i < num_elements; ++i) {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
buffer[i] = static_cast<size_t>(state);
}
size_t bw_iterations = TARGET_BYTES / size;
if (bw_iterations == 0) bw_iterations = 1;
size_t accumulator = 0;
size_t elements_per_stride = STRIDE_BYTES / sizeof(size_t);
// Warm-up pass
for (size_t j = 0; j < num_elements; j += elements_per_stride) {
accumulator += buffer[j];
}
auto start_bw = chrono::high_resolution_clock::now();
for (size_t i = 0; i < bw_iterations; ++i) {
for (size_t j = 0; j < num_elements; j += elements_per_stride) {
accumulator += buffer[j];
}
}
auto end_bw = chrono::high_resolution_clock::now();
chrono::duration<double> duration_bw = end_bw - start_bw;
volatile size_t sink_bw = accumulator;
double bytes_read = static_cast<double>(bw_iterations) * (size / STRIDE_BYTES) * STRIDE_BYTES;
double gb_per_sec = (bytes_read / static_cast<double>(GB)) / duration_bw.count();
// ==========================================
// LATENCY MEASUREMENT (Random Chasing)
// ==========================================
size_t num_cache_lines = size / STRIDE_BYTES;
vector<size_t> cycle(num_cache_lines);
for (size_t i = 0; i < num_cache_lines; ++i) {
cycle[i] = i;
}
// Sattolo's algorithm for a single, unbroken permutation cycle
for (size_t i = num_cache_lines - 1; i > 0; --i) {
uniform_int_distribution<size_t> dist(0, i - 1);
size_t j = dist(rng);
swap(cycle[i], cycle[j]);
}
// Write the cycle links directly into the stride boundaries of the buffer
for (size_t i = 0; i < num_cache_lines; ++i) {
buffer[i * elements_per_stride] = cycle[i] * elements_per_stride;
}
size_t current_index = 0;
// Warm-up pass for latency
for (size_t i = 0; i < num_cache_lines; ++i) {
current_index = buffer[current_index];
}
// Scale iterations so tiny buffers don't finish too quickly to measure
size_t lat_iterations = max(num_cache_lines * 2, LATENCY_ITERATIONS);
auto start_lat = chrono::high_resolution_clock::now();
// The dependency chain: CPU cannot fetch the next index until the current load completes
for (size_t i = 0; i < lat_iterations; ++i) {
current_index = buffer[current_index];
}
auto end_lat = chrono::high_resolution_clock::now();
chrono::duration<double, std::nano> duration_lat = end_lat - start_lat;
// Prevent compiler from optimizing away the loop
volatile size_t sink_lat = current_index;
double latency_ns = duration_lat.count() / static_cast<double>(lat_iterations);
cout << setw(15) << left << formatSize(size)
<< setw(20) << left << fixed << setprecision(3) << gb_per_sec
<< fixed << setprecision(2) << latency_ns << "\n";
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment