# Makefile for the project. # Provides targets for building, running, benchmarking, testing, and visualization. # # Main commands: # make run run the simulation, producing a CSV file # containing pedestrian states at each iteration # make video generate a video of the simulation # make run-bench run benchmark measurements # make test run all unit tests # # Default parameters can be overridden from the shell, as follows: # (parameters can be provided selectively and in any order) # # make run N_P= N_ITER= CSV= # make video N_P= N_ITER= CSV= MP4= # make run-bench N_P= N_ITER= # Select the C++ compiler. # Prefer g++ when available, otherwise fall back to clang++. CXX := $(shell command -v g++ || command -v clang++) ifeq ($(CXX),) $(error No C++ compiler found. Please install g++ or clang++) endif ARCH := $(shell uname -m) # Prepend sudo on ARM machines when benchmarking. # This is needed to access the PMU performance counters. ifeq ($(ARCH),arm64) SUDO_BENCH := sudo else ifeq ($(ARCH),aarch64) SUDO_BENCH := sudo else SUDO_BENCH := endif # Compiler flags: # -O3 enable high-level optimization # -march=native optimize for the current machine # -std=c++23 use the C++23 standard # -Wall -Wextra enable useful warnings # -flto enable link-time optimization across .cpp files, allowing # cross-file optimizations such as function inlining CXXFLAGS := -O3 -march=native -std=c++23 -Wall -Wextra -flto VARIANT ?= baseline # sleef is only needed for the avx512 variant (used by SLEEF intrinsics there) ifeq ($(ARCH),x86_64) ifneq ($(filter avx512_%,$(VARIANT)),) CXXFLAGS += -lsleef endif endif # sleef is only needed for the avx512 variant (used by SLEEF intrinsics there) ifeq ($(ARCH),x86_64) ifneq ($(filter avx512_%,$(VARIANT)),) CXXFLAGS += -lsleef endif endif VARIANT_SRC := ./variants/variant_$(VARIANT).cpp ifneq ($(filter float_%,$(VARIANT)),) MAIN_SRC := main-float.cpp else MAIN_SRC := main.cpp endif SRC := $(MAIN_SRC) $(VARIANT_SRC) HEADERS := ./headers/board.hpp ./headers/pedestrian.hpp ./headers/vector2d.hpp ./headers/constants.hpp ./headers/util.hpp TEST_SRC := ./tests/test_vector2d.cpp ./tests/test_board.cpp ./tests/test_forces.cpp $(VARIANT_SRC) TEST_TARGET := run_tests TEST_LIBS := -lgtest -lgtest_main -pthread TARGET := main_$(VARIANT) # Default simulation parameters and output files. # These can be overridden from the make command line; see examples above. N_P ?= 60 # Default number of pedestrians. N_ITER ?= 4000 # Default number of simulation iterations. CSV ?= positions.csv # Default CSV output file. MP4 ?= simulation.mp4 # Default visualization video output file. # Enable/disable non-reproducible random seeding. # Default: RANDOM_SEED=0 uses the fixed seed for reproducibility. # Use RANDOM_SEED=1 to seed from std::random_device. RANDOM_SEED ?= 0 # flopcounter enabled/disabled, no impact on runtime when running with 0 # default = 0 = disabled # 1 = enabled # if set to 0 --> compiler will swap all function calls with ((void)0) # using -O3 will remove all ((void)0) FLOPCOUNT ?= 0 CXXFLAGS += -DFLOPCOUNT=$(FLOPCOUNT) # Pass random-seeding mode to the C++ code. CXXFLAGS += -DRANDOM_SEED=$(RANDOM_SEED) # Roofline benchmarcking mode enabled/disabled # default = 0 = disabled (hot cache, for normal performance plot measurments) # 1 = enabled (cold cache, for roofline plot measurements) ROOFLINE ?= 0 CXXFLAGS += -DROOFLINE=$(ROOFLINE) # Benchmark suite parameters. BENCH_SIZES ?= 50 100 200 500 1000 2000 5000 BENCH_ITER ?= 1000 # Verification parameters ifneq ($(filter float_%,$(VARIANT)),) # Float verify uses a short horizon (500 iters). The simulation is chaotic - a 1-ULP perturbation # amplifies by ~exp(t/200) - so floats can't hold bit-equality across variants over 4000 iters # no matter what we do (different layouts -> different FMA placements -> 1-ULP drift -> meters by t=4000). # At 500 iters the amplifier is still small: AVX2 variants drift max ~4mm vs the scalar reference, # well under 1cm, and real bugs still show as O(m) errors. 4000 iters drifts to meters even when # the variant is correct, so that horizon is useless for verify. REFERENCE_N_ITER ?= 500 REFERENCE_CSV ?= reference/baseline_float_n60_i500.csv else REFERENCE_N_ITER ?= 4000 REFERENCE_CSV ?= reference/baseline_n60_i4000.csv endif REFERENCE_N_P ?= 60 VERIFY_CSV := verify_$(VARIANT).csv .PHONY: all main run bench run-bench bench-suite video asm test clean clean-all verify all: main # make main : compile the main executable with benchmarking disabled. main: clean $(CXX) $(SRC) -o $(TARGET) $(CXXFLAGS) # make run : compile the code and run the simulation without generating a video. run: main ./$(TARGET) $(N_P) $(N_ITER) $(CSV) # make verify # sanity check (baseline vs reference) # make verify VARIANT=v1_cutoff # check a specific variant verify: main ./$(TARGET) $(REFERENCE_N_P) $(REFERENCE_N_ITER) $(VERIFY_CSV) @if python3 ./scripts/verify.py $(REFERENCE_CSV) $(VERIFY_CSV); then \ echo "PASS: variant '$(VARIANT)' matches $(REFERENCE_CSV)"; \ rm -f $(VERIFY_CSV); \ else \ echo "FAIL: variant '$(VARIANT)' differs from $(REFERENCE_CSV) (kept $(VERIFY_CSV) for inspection)"; \ exit 1; \ fi # make bench : compile the main executable with benchmarking enabled. bench: CXXFLAGS += -DBENCHMARK=1 bench: clean $(SUDO_BENCH) $(CXX) $(SRC) -o $(TARGET) $(CXXFLAGS) # make run-bench : compile and run benchmark measurements. run-bench: bench $(SUDO_BENCH) ./$(TARGET) $(N_P) $(N_ITER) # make bench-suite : compile once and run benchmarks across multiple pedestrian counts. # Results are printed to stdout; redirect to a file to save them. # make bench-suite > benchmarks/baseline.txt # make bench-suite VARIANT=v1_cutoff BENCH_SIZES="100 500 1000" > benchmarks/v1_cutoff.txt bench-suite: bench @for n in $(BENCH_SIZES); do \ echo "=== N_P=$$n N_ITER=$(BENCH_ITER) ==="; \ $(SUDO_BENCH) ./$(TARGET) $$n $(BENCH_ITER); \ echo ""; \ done # make video : compile the code, run the simulation, and generate the video. video: run python3 ./scripts/visualization.py $(CSV) $(MP4) # make test : compile and run all unit tests. test: clean $(CXX) $(TEST_SRC) $(TEST_LIBS) -o $(TEST_TARGET) $(CXXFLAGS) ./$(TEST_TARGET) # make asm : compile and generate assembly files. ASM_CPP_SRCS := main.cpp main-float.cpp $(VARIANT_SRC) ASM_HPP_SRCS := ./headers/util.hpp ASM_SRCS := $(ASM_CPP_SRCS) $(ASM_HPP_SRCS) ASM_TARGETS := $(patsubst %.cpp,%.s,$(patsubst %.hpp,%.s,$(ASM_SRCS))) asm: clean $(foreach src,$(ASM_CPP_SRCS),$(CXX) -S $(src) -o $(src:.cpp=.s) $(CXXFLAGS); ) $(foreach src,$(ASM_HPP_SRCS),$(CXX) -x c++ -S $(src) -o $(src:.hpp=.s) $(CXXFLAGS); ) %.s: %.cpp $(HEADERS) $(CXX) -S $< -o $@ $(CXXFLAGS) # make clean : delete generated files from previous builds. clean: rm -f main_* $(SIZES_TARGET) $(TEST_TARGET) *.o *.s variants/*.s variants/*.o headers/*.s headers/*.o # make clean-all : delete all generated files from previous builds and runs. # WARNING: this deletes all *.csv and *.mp4 files in this directory. # Move any output files you want to keep to another directory before running it. clean-all: clean rm -f *.csv *.mp4