In this lab you will learn profiling by investigating a real C++ program that applies convolution filters to an image. The program supports two convolution methods:
- naive (direct spatial convolution)
- fft (FFT-based convolution)
Your goal is not to “make the code faster.”
Your goal is to answer, with evidence:
- Where does the runtime go?
- Why does performance differ between naive and FFT?
- When is FFT slower, and when can it win?
What profiling is (and what it is not)
Profiling answers: where does the runtime go? It helps you find hot spots (functions/loops that dominate total time), and it helps you verify that your optimization efforts are aimed at the right place.
- Profiling is not debugging.
- Profiling is not guessing.
- We measure, change one thing, measure again.
Two useful ideas:
- Wall-clock timing: “How long did the whole thing take?”
- Sampling profilers: “Which functions/lines are we spending time in?”
In this lab, chrono timing is already built into the program. Your job is to use:
perf(Linux)- Instruments Time Profiler (macOS)
Important: FFT is NOT always faster
Do not assume “FFT beats naive.”
- For small kernels (3×3 sharpen/edge/emboss), naive typically wins because FFT has large overhead.
- FFT becomes competitive only when the kernel is large enough (large blur), and implementation constants are reasonable.
Your job is to measure and then explain your results.
Part 0 — Setup and sanity check
Build
make clean make
Use the canonical input image
All commands below assume the input image is:
data/oldkenyon.ppm
Sanity run (confirm program works)
./imgconv --in data/oldkenyon.ppm --filter emboss --method naive --repeat 5 --out /tmp/emboss_naive.pgm ./imgconv --in data/oldkenyon.ppm --filter emboss --method fft --repeat 5 --out /tmp/emboss_fft.pgm
If both images look reasonable, you are ready to measure.
Part 1 — Timing experiments (chrono built-in)
Run each command at least twice. Use the average reported by the program.
Experiment 1: A small-kernel filter (FFT should lose)
./imgconv --in data/oldkenyon.ppm --filter emboss --method naive --repeat 10 --out /tmp/a.pgm ./imgconv --in data/oldkenyon.ppm --filter emboss --method fft --repeat 10 --out /tmp/b.pgm
Experiment 2: Larger blur (FFT might win)
./imgconv --in data/oldkenyon.ppm --filter blur --kernel-size 51 --method naive --repeat 3 --out /tmp/a.pgm ./imgconv --in data/oldkenyon.ppm --filter blur --kernel-size 51 --method fft --repeat 3 --out /tmp/b.pgm
Experiment 3: Find the “crossover” (if any)
Try blur kernel sizes:
- 3, 7, 15, 31, 51, 101
Record runtime for naive and FFT at each size.
Part 2 — Profiling option A: Linux perf (sampling profiler)
Perf answers: which functions dominate CPU time.
Build with symbols
make clean make CXXFLAGS="-O3 -g -march=native -Wall -Wextra -pedantic"
Profile FFT blur (choose a workload long enough to sample)
Increase repeat until the run lasts ~1–3 seconds.
perf record -g ./imgconv --in data/oldkenyon.ppm --filter blur --kernel-size 51 --method fft --repeat 20 --out /tmp/o.pgm perf report
Profile naive blur (same workload idea)
perf record -g ./imgconv --in data/oldkenyon.ppm --filter blur --kernel-size 51 --method naive --repeat 5 --out /tmp/o.pgm perf report
How to interpret perf report
Focus on:
- Top 1–3 entries (the hotspots)
- Self vs Children
- Call graph expansion (you used
-g)
Write down:
- the top hotspot function names
- the approximate percent
- what phase they correspond to (FFT phase, multiply phase, naive inner loop, etc.)
Part 3 — Profiling option B: macOS Instruments Time Profiler
Please refer to what we did in class to getr MAc profiling set up with xcode. Link
Instruments answers: where CPU time is spent, with a clear call tree and timeline.
Build with symbols
make clean make CXXFLAGS="-O3 -g -Wall -Wextra -pedantic"
Run Instruments (Time Profiler)
- Open Instruments
- Choose Time Profiler
- Select target:
imgconv - Program arguments:
--in data/oldkenyon.ppm --filter blur --kernel-size 51 --method fft --repeat 20 --out /tmp/o.pgm
Record, let it finish, stop.
Interpret results
In Call Tree:
- enable Hide System Libraries
- enable Invert Call Tree
- find top 1–3 hottest functions
Repeat for naive blur (lower repeat count is fine if it runs long enough):
--in data/oldkenyon.ppm --filter blur --kernel-size 51 --method naive --repeat 5 --out /tmp/o.pgm
What you submit
1) Results table
Include a table with:
- filter
- kernel size
- method
- average runtime
2) Profiler evidence
Include either:
- a short list of top hotspots + percent, OR
- a screenshot of the call tree/report (optional, but helpful)
3) Explanation (the important part)
In 1–2 pages, answer:
- Why does FFT lose badly for emboss/edge/sharpen?
- For blur, does FFT become competitive? At what kernel sizes?
- For each method, where is the CPU time going?
- Is the performance limited by compute (math) or memory movement?
Grading (quick rubric)
- Correct execution + clean results table: 30%
- Meaningful profiling evidence: 30%
- Clear explanation tied to evidence: 40%
