Elliot/measurements

This commit is contained in:
[Quy Anh] «Elliot» Nguyen
2026-05-21 16:21:07 +02:00
parent 3f26921177
commit ba19c3876f
30 changed files with 1115 additions and 33 deletions
+82 -33
View File
@@ -9,7 +9,6 @@
#include <vector>
#ifndef BENCHMARK
// Benchmarking is disabled by default.
#define BENCHMARK 0
#endif
@@ -20,20 +19,25 @@
#ifdef __x86_64__
#include "./headers/tsc_x86.h"
#include <immintrin.h>
#define TRASH_BUFFER_SIZE (16ULL * 1024 * 1024 * 2) // Size of L3 cache : 16MB
#define TRASH_BUFFER_SIZE (16ULL * 1024 * 1024 * 2) // 2x L3 (~16MB) on the x86 machine we use for measurements
#define PAGE_SIZE 4096
#endif
#ifdef __aarch64__
#include "./headers/kperf.h"
#include <arm_acle.h>
#define TRASH_BUFFER_SIZE (1502838784ULL * 2) // Size of SLC on M3 pro chips : 1.5GB
#define TRASH_BUFFER_SIZE (1502838784ULL * 2) // 2x SLC (~1.5GB) on M3 Pro
#define PAGE_SIZE 16384
#endif
#define N_OBSTACLES 2
#define WARMUP_ITERS 5
#define NUM_REPS 11
// Benchmark parameters (mostly copied from mvm.c in hw1)
#define WARMUP_ITERS 3
#define NUM_REPS 15
#define NUM_RUNS 1
#define CYCLES_REQUIRED 1e8
#define MAX_NUM_RUNS (1 << 14)
// Flushes caches by streaming reads through a large persistent buffer.
long long flush_cache() {
@@ -54,7 +58,7 @@ long long flush_cache() {
long long acc = 0;
for (size_t i = 0; i < TRASH_BUFFER_SIZE; i += CACHE_BLOCK_SIZE) {
// Access one byte per cache block is enough
// Access one byte per cache block is enough to populate the entire block
acc += trash_buffer[i];
}
@@ -90,12 +94,11 @@ int main(int argc, const char *argv[]) {
int n_iter = atoi(argv[2]);
if (n_p <= 0) {
printf("Error: <number_of_pedestrians> must be a positive integer, got '%s' instead\n", argv[1]);
printf("Error: <number_of_pedestrians> must be a positive integer, got '%s'\n", argv[1]);
return 1;
}
if (n_iter <= 0) {
printf("Error: <number_of_iterations> must be a positive integer, got '%s' instead\n", argv[2]);
printf("Error: <number_of_iterations> must be a positive integer, got '%s'\n", argv[2]);
return 1;
}
@@ -116,29 +119,35 @@ int main(int argc, const char *argv[]) {
struct performance_counters startperf, endperf;
#endif
// Warmup runs are not measured.
// Warmup runs, to bring caches, predictors, frequency to steady state. These are not measured.
// Each run gets a fresh initialized state because run_simulation mutates the
// pedestrians and board state.
for (int i = 0; i < WARMUP_ITERS; i++) {
Pedestrian *P = init_pedestrians(n_p);
Board board = init_board_without_obstacles();
run_simulation(P, board, n_p, n_iter, nullptr);
delete[] P;
}
// Store the cycle count of each measured repetition.
// The median is then used as the final timing result.
std::vector<long double> cycles_v;
cycles_v.reserve(NUM_REPS);
for (int i = 0; i < NUM_REPS; i++) {
// Calibration: finds num_runs such that one batch runs for at least
// CYCLES_REQUIRED cycles. A long batch keeps makes the timer and other
// noise overhead, negligible relative to the kernel execution cost we're
// actually trying to measure.
//
// Skipped when ROOFLINE is enabled: cold-cache measurements need
// exactly one timed simulation per batch, otherwise only the first
// call starts cold and subsequent calls measure warm-cache behavior.
// We can use large enough inputs so a single call already exceeds CYCLES_REQUIRED,
// when measuring for roofline plot.
// Note: we implemented the calibration exactly as it is done in mvm.c from hw1, we also
// used the same values for NUM_RUNS, etc
int num_runs = NUM_RUNS;
#if !ROOFLINE
while (num_runs < MAX_NUM_RUNS) {
Pedestrian *P = init_pedestrians(n_p);
Board board = init_board_without_obstacles();
#if ROOFLINE
trash_value = flush_cache();
#endif
long double cycles;
#ifdef __x86_64__
myInt64 start = start_tsc();
@@ -148,26 +157,69 @@ int main(int argc, const char *argv[]) {
startperf = kperf_get_counters();
#endif
// Disable CSV dumping during benchmarking to avoid measuring file I/O
// overhead. Initialization is also excluded from the measured region.
run_simulation(P, board, n_p, n_iter, nullptr);
for (int j = 0; j < num_runs; j++) {
run_simulation(P, board, n_p, n_iter, nullptr);
}
#ifdef __x86_64__
long double cycles = stop_tsc(start);
cycles = (long double)stop_tsc(start);
#endif
#ifdef __aarch64__
endperf = kperf_get_counters();
long double cycles = endperf.cycles - startperf.cycles;
cycles = (long double)(endperf.cycles - startperf.cycles);
#endif
delete[] P;
if (cycles >= CYCLES_REQUIRED)
break;
num_runs *= 2;
}
#endif
// Store the cycle count of each measured repetition.
// The median is then used as the final timing result.
std::vector<long double> cycles_v;
cycles_v.reserve(NUM_REPS);
// Measurement phase
for (int i = 0; i < NUM_REPS; i++) {
Pedestrian *P = init_pedestrians(n_p);
Board board = init_board_without_obstacles();
#if ROOFLINE
trash_value = flush_cache();
#endif
#ifdef __x86_64__
myInt64 start = start_tsc();
#endif
#ifdef __aarch64__
startperf = kperf_get_counters();
#endif
for (int j = 0; j < num_runs; j++) {
// Disable CSV dumping during benchmarking to avoid measuring file I/O
// overhead. Initialization is also excluded from the measured region.
run_simulation(P, board, n_p, n_iter, nullptr);
}
#ifdef __x86_64__
long double cycles = (long double)stop_tsc(start) / num_runs;
#endif
#ifdef __aarch64__
endperf = kperf_get_counters();
long double cycles = (long double)(endperf.cycles - startperf.cycles) / num_runs;
#endif
delete[] P;
cycles_v.push_back(cycles);
}
// Use the median cycle count to reduce the effect of noisy measurements.
std::sort(cycles_v.begin(), cycles_v.end());
// Use the median cycle count to reduce the effect of noisy measurements.
long double T = cycles_v[NUM_REPS / 2];
#else
@@ -189,14 +241,11 @@ int main(int argc, const char *argv[]) {
// The total work W(n) is estimated from the number of additions,
// multiplications, divisions, square roots, and exponentials performed by the
// simulation kernels.
long long add_count_per_iter = n_p * (4 + (n_p - 1) * 14 + N_OBSTACLES * 12) + n_p * 7;
long long mult_count_per_iter = n_p * (4 + (n_p - 1) * 23 + N_OBSTACLES * 18) + n_p * 16;
long long div_count_per_iter = n_p * ((n_p - 1) * 1 + N_OBSTACLES * 1) + n_p * 2;
long long sqrt_count_per_iter = n_p * ((n_p - 1) * 4 + N_OBSTACLES * 2) + n_p * 3;
long long exp_count_per_iter = n_p * ((n_p - 1) * 1 + N_OBSTACLES * 1);
long double W_per_iter = add_count_per_iter * cost_add + mult_count_per_iter * cost_mult + div_count_per_iter * cost_div + sqrt_count_per_iter * cost_sqrt + exp_count_per_iter * cost_exp;
@@ -204,6 +253,7 @@ int main(int argc, const char *argv[]) {
// Estimated memory traffic per iteration.
// TODO: refine this model to account for actual memory accesses, cache reuse,
// and the full interaction loop.
long long Q_per_iter = sizeof(Pedestrian) * n_p + sizeof(Obstacle) * N_OBSTACLES;
// Operational intensity:
@@ -212,9 +262,8 @@ int main(int argc, const char *argv[]) {
//
// where W is estimated work in flops and Q is estimated memory traffic in
// bytes.
long double I = W_per_iter / Q_per_iter;
// Total estimated work over all iterations.
long double I = W_per_iter / Q_per_iter;
long double W = W_per_iter * n_iter;
// Performance in estimated flops per cycle.